treewide: separate Lua runtime resources

Move classes required for Lua runtime support into a new `luci-lua-runtime`
package. Also replace the `luci.http` and `luci.util` classes in
`luci-lib-base` with stubbed versions interacting with the ucode based
runtime environment.

Finally merge `luci-base-ucode` into the remainders of `luci-base`.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2022-09-13 23:50:12 +02:00
parent ded8ccf93e
commit 673f38246a
86 changed files with 189 additions and 4580 deletions

7
.gitignore vendored
View file

@ -10,7 +10,10 @@ package-lock.json
modules/luci-base/src/po2lmo
modules/luci-base/src/jsmin
modules/luci-base/src/contrib/lemon
modules/luci-base/src/plural_formula.c
modules/luci-base/src/plural_formula.h
modules/luci-base/src/ucode/plural_formula.c
modules/luci-base/src/ucode/plural_formula.h
modules/luci-compat/src/contrib/lemon
modules/luci-compat/src/plural_formula.c
modules/luci-compat/src/plural_formula.h
docs/jsapi/*
!docs/jsapi/README.md

View file

@ -8,7 +8,7 @@
echo -n "Updating modules/luci-base/po/templates/base.pot ... "
./build/i18n-scan.pl \
modules/luci-base/ modules/luci-compat/ modules/luci-mod-admin-full/ \
modules/luci-base/ modules/luci-compat/ modules/luci-lua-runtime/ \
modules/luci-mod-network modules/luci-mod-status modules/luci-mod-system/ \
protocols/ themes/ \
> modules/luci-base/po/templates/base.pot

View file

@ -7,7 +7,7 @@ use strict;
my %TZ;
my $tzdin = $ARGV[0] || "/usr/share/zoneinfo";
my $tzdout = $ARGV[1] || "./modules/luci-base-ucode/ucode/zoneinfo.uc";
my $tzdout = $ARGV[1] || "./modules/luci-base/ucode/zoneinfo.uc";
local $/ = "\012";
open( ZTAB, "< $tzdin/zone.tab" ) || die "open($tzdin/zone.tab): $!";

View file

@ -6,234 +6,66 @@ local util = require "luci.util"
local coroutine = require "coroutine"
local table = require "table"
local lhttp = require "lucihttp"
local nixio = require "nixio"
local ltn12 = require "luci.ltn12"
local table, ipairs, pairs, type, tostring, tonumber, error =
table, ipairs, pairs, type, tostring, tonumber, error
local L, table, ipairs, pairs, type, error = _G.L, table, ipairs, pairs, type, error
module "luci.http"
HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size
context = util.threadlocal()
Request = util.class()
function Request.__init__(self, env, sourcein, sinkerr)
self.input = sourcein
self.error = sinkerr
-- File handler nil by default to let .content() work
self.filehandler = nil
-- HTTP-Message table
self.message = {
env = env,
headers = {},
params = urldecode_params(env.QUERY_STRING or ""),
}
self.parsed_input = false
end
function Request.formvalue(self, name, noparse)
if not noparse and not self.parsed_input then
self:_parse_input()
end
if name then
return self.message.params[name]
else
return self.message.params
end
end
function Request.formvaluetable(self, prefix)
local vals = {}
prefix = prefix and prefix .. "." or "."
if not self.parsed_input then
self:_parse_input()
end
local void = self.message.params[nil]
for k, v in pairs(self.message.params) do
if k:find(prefix, 1, true) == 1 then
vals[k:sub(#prefix + 1)] = tostring(v)
end
end
return vals
end
function Request.content(self)
if not self.parsed_input then
self:_parse_input()
end
return self.message.content, self.message.content_length
end
function Request.getcookie(self, name)
return lhttp.header_attribute("cookie; " .. (self:getenv("HTTP_COOKIE") or ""), name)
end
function Request.getenv(self, name)
if name then
return self.message.env[name]
else
return self.message.env
end
end
function Request.setfilehandler(self, callback)
self.filehandler = callback
if not self.parsed_input then
return
end
-- If input has already been parsed then uploads are stored as unlinked
-- temporary files pointed to by open file handles in the parameter
-- value table. Loop all params, and invoke the file callback for any
-- param with an open file handle.
local name, value
for name, value in pairs(self.message.params) do
if type(value) == "table" then
while value.fd do
local data = value.fd:read(1024)
local eof = (not data or data == "")
callback(value, data, eof)
if eof then
value.fd:close()
value.fd = nil
end
end
end
end
end
function Request._parse_input(self)
parse_message_body(
self.input,
self.message,
self.filehandler
)
self.parsed_input = true
end
function close()
if not context.eoh then
context.eoh = true
coroutine.yield(3)
end
if not context.closed then
context.closed = true
coroutine.yield(5)
end
L.http:close()
end
function content()
return context.request:content()
return L.http:content()
end
function formvalue(name, noparse)
return context.request:formvalue(name, noparse)
return L.http:formvalue(name, noparse)
end
function formvaluetable(prefix)
return context.request:formvaluetable(prefix)
return L.http:formvaluetable(prefix)
end
function getcookie(name)
return context.request:getcookie(name)
return L.http:getcookie(name)
end
-- or the environment table itself.
function getenv(name)
return context.request:getenv(name)
return L.http:getenv(name)
end
function setfilehandler(callback)
return context.request:setfilehandler(callback)
return L.http:setfilehandler(callback)
end
function header(key, value)
if not context.headers then
context.headers = {}
end
context.headers[key:lower()] = value
coroutine.yield(2, key, value)
L.http:header(key, value)
end
function prepare_content(mime)
if not context.headers or not context.headers["content-type"] then
if mime == "application/xhtml+xml" then
if not getenv("HTTP_ACCEPT") or
not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
mime = "text/html; charset=UTF-8"
end
header("Vary", "Accept")
end
header("Content-Type", mime)
end
L.http:prepare_content(mime)
end
function source()
return context.request.input
return L.http.input
end
function status(code, message)
code = code or 200
message = message or "OK"
context.status = code
coroutine.yield(1, code, message)
L.http:status(code, message)
end
-- This function is as a valid LTN12 sink.
-- If the content chunk is nil this function will automatically invoke close.
function write(content, src_err)
if not content then
if src_err then
error(src_err)
else
close()
end
return true
elseif #content == 0 then
return true
else
if not context.eoh then
if not context.status then
status()
end
if not context.headers or not context.headers["content-type"] then
header("Content-Type", "text/html; charset=utf-8")
end
if not context.headers["cache-control"] then
header("Cache-Control", "no-cache")
header("Expires", "0")
end
if not context.headers["x-frame-options"] then
header("X-Frame-Options", "SAMEORIGIN")
end
if not context.headers["x-xss-protection"] then
header("X-XSS-Protection", "1; mode=block")
end
if not context.headers["x-content-type-options"] then
header("X-Content-Type-Options", "nosniff")
end
context.eoh = true
coroutine.yield(3)
end
coroutine.yield(4, content)
return true
if src_err then
error(src_err)
end
return L.print(content)
end
function splice(fd, size)
@ -241,10 +73,7 @@ function splice(fd, size)
end
function redirect(url)
if url == "" then url = "/" end
status(302, "Found")
header("Location", url)
close()
L.http:redirect(url)
end
function build_querystring(q)
@ -266,35 +95,7 @@ urldecode = util.urldecode
urlencode = util.urlencode
function write_json(x)
util.serialize_json(x, write)
end
-- from given url or string. Returns a table with urldecoded values.
-- Simple parameters are stored as string values associated with the parameter
-- name within the table. Parameters with multiple values are stored as array
-- containing the corresponding values.
function urldecode_params(url, tbl)
local parser, name
local params = tbl or { }
parser = lhttp.urlencoded_parser(function (what, buffer, length)
if what == parser.TUPLE then
name, value = nil, nil
elseif what == parser.NAME then
name = lhttp.urldecode(buffer)
elseif what == parser.VALUE and name then
params[name] = lhttp.urldecode(buffer) or ""
end
return true
end)
if parser then
parser:parse((url or ""):match("[^?]*$"))
parser:parse(nil)
end
return params
L.printf('%J', x)
end
-- separated by "&". Tables are encoded as parameters with multiple values by
@ -332,223 +133,12 @@ function urlencode_params(tbl)
return table.concat(enc, "")
end
-- Content-Type. Stores all extracted data associated with its parameter name
-- in the params table within the given message object. Multiple parameter
-- values are stored as tables, ordinary ones as strings.
-- If an optional file callback function is given then it is fed with the
-- file contents chunk by chunk and only the extracted file name is stored
-- within the params table. The callback function will be called subsequently
-- with three arguments:
-- o Table containing decoded (name, file) and raw (headers) mime header data
-- o String value containing a chunk of the file data
-- o Boolean which indicates whether the current chunk is the last one (eof)
function mimedecode_message_body(src, msg, file_cb)
local parser, header, field
local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil)
parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length)
if what == parser.PART_INIT then
field = { }
elseif what == parser.HEADER_NAME then
header = buffer:lower()
elseif what == parser.HEADER_VALUE and header then
if header:lower() == "content-disposition" and
lhttp.header_attribute(buffer, nil) == "form-data"
then
field.name = lhttp.header_attribute(buffer, "name")
field.file = lhttp.header_attribute(buffer, "filename")
field[1] = field.file
end
if field.headers then
field.headers[header] = buffer
else
field.headers = { [header] = buffer }
end
elseif what == parser.PART_BEGIN then
return not field.file
elseif what == parser.PART_DATA and field.name and length > 0 then
if field.file then
if file_cb then
file_cb(field, buffer, false)
msg.params[field.name] = msg.params[field.name] or field
else
if not field.fd then
field.fd = nixio.mkstemp(field.name)
end
if field.fd then
field.fd:write(buffer)
msg.params[field.name] = msg.params[field.name] or field
end
end
else
field.value = buffer
end
elseif what == parser.PART_END and field.name then
if field.file and msg.params[field.name] then
if file_cb then
file_cb(field, "", true)
elseif field.fd then
field.fd:seek(0, "set")
end
else
local val = msg.params[field.name]
if type(val) == "table" then
val[#val+1] = field.value or ""
elseif val ~= nil then
msg.params[field.name] = { val, field.value or "" }
else
msg.params[field.name] = field.value or ""
end
end
field = nil
elseif what == parser.ERROR then
err = buffer
end
return true
end, HTTP_MAX_CONTENT)
return ltn12.pump.all(src, function (chunk)
len = len + (chunk and #chunk or 0)
if maxlen and len > maxlen + 2 then
return nil, "Message body size exceeds Content-Length"
end
if not parser or not parser:parse(chunk) then
return nil, err
end
return true
end)
end
-- Content-Type. Stores all extracted data associated with its parameter name
-- in the params table within the given message object. Multiple parameter
-- values are stored as tables, ordinary ones as strings.
function urldecode_message_body(src, msg)
local err, name, value, parser
local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil)
parser = lhttp.urlencoded_parser(function (what, buffer, length)
if what == parser.TUPLE then
name, value = nil, nil
elseif what == parser.NAME then
name = lhttp.urldecode(buffer, lhttp.DECODE_PLUS)
elseif what == parser.VALUE and name then
local val = msg.params[name]
if type(val) == "table" then
val[#val+1] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or ""
elseif val ~= nil then
msg.params[name] = { val, lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" }
else
msg.params[name] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or ""
end
elseif what == parser.ERROR then
err = buffer
end
return true
end, HTTP_MAX_CONTENT)
return ltn12.pump.all(src, function (chunk)
len = len + (chunk and #chunk or 0)
if maxlen and len > maxlen + 2 then
return nil, "Message body size exceeds Content-Length"
elseif len > HTTP_MAX_CONTENT then
return nil, "Message body size exceeds maximum allowed length"
end
if not parser or not parser:parse(chunk) then
return nil, err
end
return true
end)
end
-- This function will examine the Content-Type within the given message object
-- to select the appropriate content decoder.
-- Currently the application/x-www-urlencoded and application/form-data
-- mime types are supported. If the encountered content encoding can't be
-- handled then the whole message body will be stored unaltered as "content"
-- property within the given message object.
function parse_message_body(src, msg, filecb)
if msg.env.CONTENT_LENGTH or msg.env.REQUEST_METHOD == "POST" then
local ctype = lhttp.header_attribute(msg.env.CONTENT_TYPE, nil)
-- Is it multipart/mime ?
if ctype == "multipart/form-data" then
return mimedecode_message_body(src, msg, filecb)
-- Is it application/x-www-form-urlencoded ?
elseif ctype == "application/x-www-form-urlencoded" then
return urldecode_message_body(src, msg)
end
-- Unhandled encoding
-- If a file callback is given then feed it chunk by chunk, else
-- store whole buffer in message.content
local sink
-- If we have a file callback then feed it
if type(filecb) == "function" then
local meta = {
name = "raw",
encoding = msg.env.CONTENT_TYPE
}
sink = function( chunk )
if chunk then
return filecb(meta, chunk, false)
else
return filecb(meta, nil, true)
end
end
-- ... else append to .content
else
msg.content = ""
msg.content_length = 0
sink = function( chunk )
if chunk then
if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
msg.content = msg.content .. chunk
msg.content_length = msg.content_length + #chunk
return true
else
return nil, "POST data exceeds maximum allowed length"
end
end
return true
end
end
-- Pump data...
while true do
local ok, err = ltn12.pump.step( src, sink )
if not ok and err then
return nil, err
elseif not ok then -- eof
return true
end
end
return true
end
return false
end
context = {
request = {
formvalue = function(self, ...) return formvalue(...) end;
formvaluetable = function(self, ...) return formvaluetable(...) end;
content = function(self, ...) return content(...) end;
getcookie = function(self, ...) return getcookie(...) end;
setfilehandler = function(self, ...) return setfilehandler(...) end;
}
}

View file

@ -100,32 +100,8 @@ end
-- Scope manipulation routines
--
coxpt = setmetatable({}, { __mode = "kv" })
local tl_meta = {
__mode = "k",
__index = function(self, key)
local t = rawget(self, coxpt[coroutine.running()]
or coroutine.running() or 0)
return t and t[key]
end,
__newindex = function(self, key, value)
local c = coxpt[coroutine.running()] or coroutine.running() or 0
local r = rawget(self, c)
if not r then
rawset(self, c, { [key] = value })
else
r[key] = value
end
end
}
-- the current active coroutine. A thread local store is private a table object
-- whose values can't be accessed from outside of the running coroutine.
function threadlocal(tbl)
return setmetatable(tbl or {}, tl_meta)
return tbl or {}
end
@ -772,7 +748,6 @@ function coxpcall(f, err, ...)
co = coroutine.create(newf)
end
coromap[co] = current
coxpt[co] = coxpt[current] or current or 0
return performResume(err, co, ...)
end
end

View file

@ -1,45 +0,0 @@
#
# Copyright (C) 2022 Jo-Philipp Wich <jo@mein.io>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-base-ucode
LUCI_TYPE:=mod
LUCI_BASENAME:=base-ucode
LUCI_TITLE:=LuCI core ucode runtime
LUCI_DEPENDS:=\
+luci-base \
+ucode \
+ucode-mod-fs \
+ucode-mod-uci \
+ucode-mod-ubus \
+ucode-mod-math \
+ucode-mod-lua \
+ucode-mod-html \
+rpcd-mod-ucode \
+liblucihttp-ucode
PKG_LICENSE:=MIT
define Package/luci-base-ucode/postinst
#!/bin/sh
if [ -z "$${PKG_INSTROOT}" ] && [ -f /etc/config/uhttpd ]; then
if ! uci -q get uhttpd.main.ucode_prefix | grep -sq /cgi-bin/luci-ucode; then
uci add_list uhttpd.main.ucode_prefix='/cgi-bin/luci-ucode=/usr/share/ucode/luci/uhttpd.uc'
uci commit uhttpd
service uhttpd reload
fi
fi
exit 0
endef
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -1,41 +0,0 @@
#!/usr/bin/env ucode
'use strict';
import { stdin, stdout } from 'fs';
import dispatch from 'luci.dispatcher';
import request from 'luci.http';
const input_bufsize = 4096;
let input_available = +getenv('CONTENT_LENGTH') || 0;
function read(len) {
if (input_available == 0) {
stdin.close();
return null;
}
let chunk = stdin.read(min(input_available, len ?? input_bufsize, input_bufsize));
if (chunk == null) {
input_available = 0;
stdin.close();
}
else {
input_available -= length(chunk);
}
return chunk;
}
function write(data) {
return stdout.write(data);
}
let req = request(getenv(), read, write);
dispatch(req);
req.close();

View file

@ -1,144 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2010-2018 Jo-Philipp Wich <jo@mein.io>
-- Licensed to the public under the Apache License 2.0.
local util = require "luci.util"
local coroutine = require "coroutine"
local table = require "table"
local lhttp = require "lucihttp"
local L, table, ipairs, pairs, type, error = _G.L, table, ipairs, pairs, type, error
module "luci.http"
HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size
function close()
L.http:close()
end
function content()
return L.http:content()
end
function formvalue(name, noparse)
return L.http:formvalue(name, noparse)
end
function formvaluetable(prefix)
return L.http:formvaluetable(prefix)
end
function getcookie(name)
return L.http:getcookie(name)
end
-- or the environment table itself.
function getenv(name)
return L.http:getenv(name)
end
function setfilehandler(callback)
return L.http:setfilehandler(callback)
end
function header(key, value)
L.http:header(key, value)
end
function prepare_content(mime)
L.http:prepare_content(mime)
end
function source()
return L.http.input
end
function status(code, message)
L.http:status(code, message)
end
-- This function is as a valid LTN12 sink.
-- If the content chunk is nil this function will automatically invoke close.
function write(content, src_err)
if src_err then
error(src_err)
end
return L.print(content)
end
function splice(fd, size)
coroutine.yield(6, fd, size)
end
function redirect(url)
L.http:redirect(url)
end
function build_querystring(q)
local s, n, k, v = {}, 1, nil, nil
for k, v in pairs(q) do
s[n+0] = (n == 1) and "?" or "&"
s[n+1] = util.urlencode(k)
s[n+2] = "="
s[n+3] = util.urlencode(v)
n = n + 4
end
return table.concat(s, "")
end
urldecode = util.urldecode
urlencode = util.urlencode
function write_json(x)
L.printf('%J', x)
end
-- separated by "&". Tables are encoded as parameters with multiple values by
-- repeating the parameter name with each value.
function urlencode_params(tbl)
local k, v
local n, enc = 1, {}
for k, v in pairs(tbl) do
if type(v) == "table" then
local i, v2
for i, v2 in ipairs(v) do
if enc[1] then
enc[n] = "&"
n = n + 1
end
enc[n+0] = lhttp.urlencode(k)
enc[n+1] = "="
enc[n+2] = lhttp.urlencode(v2)
n = n + 3
end
else
if enc[1] then
enc[n] = "&"
n = n + 1
end
enc[n+0] = lhttp.urlencode(k)
enc[n+1] = "="
enc[n+2] = lhttp.urlencode(v)
n = n + 3
end
end
return table.concat(enc, "")
end
context = {
request = {
formvalue = function(self, ...) return formvalue(...) end;
formvaluetable = function(self, ...) return formvaluetable(...) end;
content = function(self, ...) return content(...) end;
getcookie = function(self, ...) return getcookie(...) end;
setfilehandler = function(self, ...) return setfilehandler(...) end;
}
}

View file

@ -1,786 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local io = require "io"
local math = require "math"
local table = require "table"
local debug = require "debug"
local ldebug = require "luci.debug"
local string = require "string"
local coroutine = require "coroutine"
local tparser = require "luci.template.parser"
local json = require "luci.jsonc"
local lhttp = require "lucihttp"
local _ubus = require "ubus"
local _ubus_connection = nil
local getmetatable, setmetatable = getmetatable, setmetatable
local rawget, rawset, unpack, select = rawget, rawset, unpack, select
local tostring, type, assert, error = tostring, type, assert, error
local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring
local require, pcall, xpcall = require, pcall, xpcall
local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit
local L = _G.L
module "luci.util"
--
-- Pythonic string formatting extension
--
getmetatable("").__mod = function(a, b)
local ok, res
if not b then
return a
elseif type(b) == "table" then
local k, _
for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end
ok, res = pcall(a.format, a, unpack(b))
if not ok then
error(res, 2)
end
return res
else
if type(b) == "userdata" then b = tostring(b) end
ok, res = pcall(a.format, a, b)
if not ok then
error(res, 2)
end
return res
end
end
--
-- Class helper routines
--
-- Instantiates a class
local function _instantiate(class, ...)
local inst = setmetatable({}, {__index = class})
if inst.__init__ then
inst:__init__(...)
end
return inst
end
-- The class object can be instantiated by calling itself.
-- Any class functions or shared parameters can be attached to this object.
-- Attaching a table to the class object makes this table shared between
-- all instances of this class. For object parameters use the __init__ function.
-- Classes can inherit member functions and values from a base class.
-- Class can be instantiated by calling them. All parameters will be passed
-- to the __init__ function of this class - if such a function exists.
-- The __init__ function must be used to set any object parameters that are not shared
-- with other objects of this class. Any return values will be ignored.
function class(base)
return setmetatable({}, {
__call = _instantiate,
__index = base
})
end
function instanceof(object, class)
local meta = getmetatable(object)
while meta and meta.__index do
if meta.__index == class then
return true
end
meta = getmetatable(meta.__index)
end
return false
end
--
-- Scope manipulation routines
--
coxpt = setmetatable({}, { __mode = "kv" })
local tl_meta = {
__mode = "k",
__index = function(self, key)
local t = rawget(self, coxpt[coroutine.running()]
or coroutine.running() or 0)
L.http:write("<!-- __index(%s/%s, %s): %s -->\n" %{ tostring(self), tostring(coxpt[coroutine.running()] or coroutine.running() or 0), key, tostring(t and t[key]) })
return t and t[key]
end,
__newindex = function(self, key, value)
L.http:write("<!-- __newindex(%s/%s, %s, %s) -->\n" %{ tostring(self), tostring(coxpt[coroutine.running()] or coroutine.running() or 0), key, tostring(value) })
local c = coxpt[coroutine.running()] or coroutine.running() or 0
local r = rawget(self, c)
if not r then
rawset(self, c, { [key] = value })
else
r[key] = value
end
end
}
-- the current active coroutine. A thread local store is private a table object
-- whose values can't be accessed from outside of the running coroutine.
function threadlocal(tbl)
return tbl or {} --setmetatable(tbl or {}, tl_meta)
end
--
-- Debugging routines
--
function perror(obj)
return io.stderr:write(tostring(obj) .. "\n")
end
function dumptable(t, maxdepth, i, seen)
i = i or 0
seen = seen or setmetatable({}, {__mode="k"})
for k,v in pairs(t) do
perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v))
if type(v) == "table" and (not maxdepth or i < maxdepth) then
if not seen[v] then
seen[v] = true
dumptable(v, maxdepth, i+1, seen)
else
perror(string.rep("\t", i) .. "*** RECURSION ***")
end
end
end
end
--
-- String and data manipulation routines
--
-- compatibility wrapper for xml.pcdata
function pcdata(value)
local xml = require "luci.xml"
perror("luci.util.pcdata() has been replaced by luci.xml.pcdata() - Please update your code.")
return xml.pcdata(value)
end
function urlencode(value)
if value ~= nil then
local str = tostring(value)
return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL)
or str
end
return nil
end
function urldecode(value, decode_plus)
if value ~= nil then
local flag = decode_plus and lhttp.DECODE_PLUS or 0
local str = tostring(value)
return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED + flag)
or str
end
return nil
end
-- compatibility wrapper for xml.striptags
function striptags(value)
local xml = require "luci.xml"
perror("luci.util.striptags() has been replaced by luci.xml.striptags() - Please update your code.")
return xml.striptags(value)
end
function shellquote(value)
return string.format("'%s'", string.gsub(value or "", "'", "'\\''"))
end
-- for bash, ash and similar shells single-quoted strings are taken
-- literally except for single quotes (which terminate the string)
-- (and the exception noted below for dash (-) at the start of a
-- command line parameter).
function shellsqescape(value)
local res
res, _ = string.gsub(value, "'", "'\\''")
return res
end
-- bash, ash and other similar shells interpret a dash (-) at the start
-- of a command-line parameters as an option indicator regardless of
-- whether it is inside a single-quoted string. It must be backlash
-- escaped to resolve this. This requires in some funky special-case
-- handling. It may actually be a property of the getopt function
-- rather than the shell proper.
function shellstartsqescape(value)
res, _ = string.gsub(value, "^%-", "\\-")
return shellsqescape(res)
end
-- containing the resulting substrings. The optional max parameter specifies
-- the number of bytes to process, regardless of the actual length of the given
-- string. The optional last parameter, regex, specifies whether the separator
-- sequence is interpreted as regular expression.
-- pattern as regular expression (optional, default is false)
function split(str, pat, max, regex)
pat = pat or "\n"
max = max or #str
local t = {}
local c = 1
if #str == 0 then
return {""}
end
if #pat == 0 then
return nil
end
if max == 0 then
return str
end
repeat
local s, e = str:find(pat, c, not regex)
max = max - 1
if s and max < 0 then
t[#t+1] = str:sub(c)
else
t[#t+1] = str:sub(c, s and s - 1)
end
c = e and e + 1 or #str + 1
until not s or max < 0
return t
end
function trim(str)
return (str:gsub("^%s*(.-)%s*$", "%1"))
end
function cmatch(str, pat)
local count = 0
for _ in str:gmatch(pat) do count = count + 1 end
return count
end
-- one token per invocation, the tokens are separated by whitespace. If the
-- input value is a table, it is transformed into a string first. A nil value
-- will result in a valid iterator which aborts with the first invocation.
function imatch(v)
if type(v) == "table" then
local k = nil
return function()
k = next(v, k)
return v[k]
end
elseif type(v) == "number" or type(v) == "boolean" then
local x = true
return function()
if x then
x = false
return tostring(v)
end
end
elseif type(v) == "userdata" or type(v) == "string" then
return tostring(v):gmatch("%S+")
end
return function() end
end
-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
-- Recognized units are:
-- o "y" - one year (60*60*24*366)
-- o "m" - one month (60*60*24*31)
-- o "w" - one week (60*60*24*7)
-- o "d" - one day (60*60*24)
-- o "h" - one hour (60*60)
-- o "min" - one minute (60)
-- o "kb" - one kilobyte (1024)
-- o "mb" - one megabyte (1024*1024)
-- o "gb" - one gigabyte (1024*1024*1024)
-- o "kib" - one si kilobyte (1000)
-- o "mib" - one si megabyte (1000*1000)
-- o "gib" - one si gigabyte (1000*1000*1000)
function parse_units(ustr)
local val = 0
-- unit map
local map = {
-- date stuff
y = 60 * 60 * 24 * 366,
m = 60 * 60 * 24 * 31,
w = 60 * 60 * 24 * 7,
d = 60 * 60 * 24,
h = 60 * 60,
min = 60,
-- storage sizes
kb = 1024,
mb = 1024 * 1024,
gb = 1024 * 1024 * 1024,
-- storage sizes (si)
kib = 1000,
mib = 1000 * 1000,
gib = 1000 * 1000 * 1000
}
-- parse input string
for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
local num = spec:gsub("[^0-9%.]+$","")
local spn = spec:gsub("^[0-9%.]+", "")
if map[spn] or map[spn:sub(1,1)] then
val = val + num * ( map[spn] or map[spn:sub(1,1)] )
else
val = val + num
end
end
return val
end
-- also register functions above in the central string class for convenience
string.split = split
string.trim = trim
string.cmatch = cmatch
string.parse_units = parse_units
function append(src, ...)
for i, a in ipairs({...}) do
if type(a) == "table" then
for j, v in ipairs(a) do
src[#src+1] = v
end
else
src[#src+1] = a
end
end
return src
end
function combine(...)
return append({}, ...)
end
function contains(table, value)
for k, v in pairs(table) do
if value == v then
return k
end
end
return false
end
-- Both table are - in fact - merged together.
function update(t, updates)
for k, v in pairs(updates) do
t[k] = v
end
end
function keys(t)
local keys = { }
if t then
for k, _ in kspairs(t) do
keys[#keys+1] = k
end
end
return keys
end
function clone(object, deep)
local copy = {}
for k, v in pairs(object) do
if deep and type(v) == "table" then
v = clone(v, deep)
end
copy[k] = v
end
return setmetatable(copy, getmetatable(object))
end
-- Serialize the contents of a table value.
function _serialize_table(t, seen)
assert(not seen[t], "Recursion detected.")
seen[t] = true
local data = ""
local idata = ""
local ilen = 0
for k, v in pairs(t) do
if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
k = serialize_data(k, seen)
v = serialize_data(v, seen)
data = data .. ( #data > 0 and ", " or "" ) ..
'[' .. k .. '] = ' .. v
elseif k > ilen then
ilen = k
end
end
for i = 1, ilen do
local v = serialize_data(t[i], seen)
idata = idata .. ( #idata > 0 and ", " or "" ) .. v
end
return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
end
-- with loadstring().
function serialize_data(val, seen)
seen = seen or setmetatable({}, {__mode="k"})
if val == nil then
return "nil"
elseif type(val) == "number" then
return val
elseif type(val) == "string" then
return "%q" % val
elseif type(val) == "boolean" then
return val and "true" or "false"
elseif type(val) == "function" then
return "loadstring(%q)" % get_bytecode(val)
elseif type(val) == "table" then
return "{ " .. _serialize_table(val, seen) .. " }"
else
return '"[unhandled data type:' .. type(val) .. ']"'
end
end
function restore_data(str)
return loadstring("return " .. str)()
end
--
-- Byte code manipulation routines
--
-- will be stripped before it is returned.
function get_bytecode(val)
local code
if type(val) == "function" then
code = string.dump(val)
else
code = string.dump( loadstring( "return " .. serialize_data(val) ) )
end
return code -- and strip_bytecode(code)
end
-- numbers and debugging numbers will be discarded. Original version by
-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
function strip_bytecode(code)
local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
local subint
if endian == 1 then
subint = function(code, i, l)
local val = 0
for n = l, 1, -1 do
val = val * 256 + code:byte(i + n - 1)
end
return val, i + l
end
else
subint = function(code, i, l)
local val = 0
for n = 1, l, 1 do
val = val * 256 + code:byte(i + n - 1)
end
return val, i + l
end
end
local function strip_function(code)
local count, offset = subint(code, 1, size)
local stripped = { string.rep("\0", size) }
local dirty = offset + count
offset = offset + count + int * 2 + 4
offset = offset + int + subint(code, offset, int) * ins
count, offset = subint(code, offset, int)
for n = 1, count do
local t
t, offset = subint(code, offset, 1)
if t == 1 then
offset = offset + 1
elseif t == 4 then
offset = offset + size + subint(code, offset, size)
elseif t == 3 then
offset = offset + num
elseif t == 254 or t == 9 then
offset = offset + lnum
end
end
count, offset = subint(code, offset, int)
stripped[#stripped+1] = code:sub(dirty, offset - 1)
for n = 1, count do
local proto, off = strip_function(code:sub(offset, -1))
stripped[#stripped+1] = proto
offset = offset + off - 1
end
offset = offset + subint(code, offset, int) * int + int
count, offset = subint(code, offset, int)
for n = 1, count do
offset = offset + subint(code, offset, size) + size + int * 2
end
count, offset = subint(code, offset, int)
for n = 1, count do
offset = offset + subint(code, offset, size) + size
end
stripped[#stripped+1] = string.rep("\0", int * 3)
return table.concat(stripped), offset
end
return code:sub(1,12) .. strip_function(code:sub(13,-1))
end
--
-- Sorting iterator functions
--
function _sortiter( t, f )
local keys = { }
local k, v
for k, v in pairs(t) do
keys[#keys+1] = k
end
local _pos = 0
table.sort( keys, f )
return function()
_pos = _pos + 1
if _pos <= #keys then
return keys[_pos], t[keys[_pos]], _pos
end
end
end
-- the provided callback function.
function spairs(t,f)
return _sortiter( t, f )
end
-- The table pairs are sorted by key.
function kspairs(t)
return _sortiter( t )
end
-- The table pairs are sorted by value.
function vspairs(t)
return _sortiter( t, function (a,b) return t[a] < t[b] end )
end
--
-- System utility functions
--
function bigendian()
return string.byte(string.dump(function() end), 7) == 0
end
function exec(command)
local pp = io.popen(command)
local data = pp:read("*a")
pp:close()
return data
end
function execi(command)
local pp = io.popen(command)
return pp and function()
local line = pp:read()
if not line then
pp:close()
end
return line
end
end
-- Deprecated
function execl(command)
local pp = io.popen(command)
local line = ""
local data = {}
while true do
line = pp:read()
if (line == nil) then break end
data[#data+1] = line
end
pp:close()
return data
end
local ubus_codes = {
"INVALID_COMMAND",
"INVALID_ARGUMENT",
"METHOD_NOT_FOUND",
"NOT_FOUND",
"NO_DATA",
"PERMISSION_DENIED",
"TIMEOUT",
"NOT_SUPPORTED",
"UNKNOWN_ERROR",
"CONNECTION_FAILED"
}
local function ubus_return(...)
if select('#', ...) == 2 then
local rv, err = select(1, ...), select(2, ...)
if rv == nil and type(err) == "number" then
return nil, err, ubus_codes[err]
end
end
return ...
end
function ubus(object, method, data, path, timeout)
if not _ubus_connection then
_ubus_connection = _ubus.connect(path, timeout)
assert(_ubus_connection, "Unable to establish ubus connection")
end
if object and method then
if type(data) ~= "table" then
data = { }
end
return ubus_return(_ubus_connection:call(object, method, data))
elseif object then
return _ubus_connection:signatures(object)
else
return _ubus_connection:objects()
end
end
function serialize_json(x, cb)
local js = json.stringify(x)
if type(cb) == "function" then
cb(js)
else
return js
end
end
function libpath()
return require "nixio.fs".dirname(ldebug.__file__)
end
function checklib(fullpathexe, wantedlib)
local fs = require "nixio.fs"
local haveldd = fs.access('/usr/bin/ldd')
local haveexe = fs.access(fullpathexe)
if not haveldd or not haveexe then
return false
end
local libs = exec(string.format("/usr/bin/ldd %s", shellquote(fullpathexe)))
if not libs then
return false
end
for k, v in ipairs(split(libs)) do
if v:find(wantedlib) then
return true
end
end
return false
end
-------------------------------------------------------------------------------
-- Coroutine safe xpcall and pcall versions
--
-- Encapsulates the protected calls with a coroutine based loop, so errors can
-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines
-- yielding inside the call to pcall or xpcall.
--
-- Authors: Roberto Ierusalimschy and Andre Carregal
-- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas
--
-- Copyright 2005 - Kepler Project
--
-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Implements xpcall with coroutines
-------------------------------------------------------------------------------
local coromap = setmetatable({}, { __mode = "k" })
local function handleReturnValue(err, co, status, ...)
if not status then
return false, err(debug.traceback(co, (...)), ...)
end
if coroutine.status(co) == 'suspended' then
return performResume(err, co, coroutine.yield(...))
else
return true, ...
end
end
function performResume(err, co, ...)
return handleReturnValue(err, co, coroutine.resume(co, ...))
end
local function id(trace, ...)
return trace
end
function coxpcall(f, err, ...)
local current = coroutine.running()
if not current then
if err == id then
return pcall(f, ...)
else
if select("#", ...) > 0 then
local oldf, params = f, { ... }
f = function() return oldf(unpack(params)) end
end
return xpcall(f, err)
end
else
local res, co = pcall(coroutine.create, f)
if not res then
local newf = function(...) return f(...) end
co = coroutine.create(newf)
end
coromap[co] = current
coxpt[co] = coxpt[current] or current or 0
return performResume(err, co, ...)
end
end
function copcall(f, ...)
return coxpcall(f, id, ...)
end

View file

@ -1,28 +0,0 @@
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -DNDEBUG -c -o $@ $<
contrib/lemon: contrib/lemon.c contrib/lempar.c
cc -o contrib/lemon $<
lib/plural_formula.c: lib/plural_formula.y contrib/lemon
./contrib/lemon -q $<
lib/lmo.c: lib/plural_formula.c
core.so: lib/luci.o lib/lmo.o lib/plural_formula.o
$(CC) $(LDFLAGS) -shared -o $@ $^
version.uc:
echo "export const revision = '$(LUCI_VERSION)', branch = '$(LUCI_GITBRANCH)';" > $@
clean:
rm -f contrib/lemon lib/*.o lib/plural_formula.c lib/plural_formula.h core.so version.uc
compile: core.so version.uc
install: compile
mkdir -p $(DESTDIR)/usr/lib/ucode/luci
cp core.so $(DESTDIR)/usr/lib/ucode/luci/core.so
mkdir -p $(DESTDIR)/usr/share/ucode/luci
cp version.uc $(DESTDIR)/usr/share/ucode/luci/version.uc

View file

@ -1,5 +1,5 @@
#
# Copyright (C) 2008-2015 The LuCI Team <luci@lists.subsignal.org>
# Copyright (C) 2022 Jo-Philipp Wich <jo@mein.io>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
@ -11,8 +11,20 @@ PKG_NAME:=luci-base
LUCI_TYPE:=mod
LUCI_BASENAME:=base
LUCI_TITLE:=LuCI core libraries
LUCI_DEPENDS:=+lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc +liblucihttp-lua +luci-lib-base +rpcd-mod-file +rpcd-mod-luci +cgi-io
LUCI_TITLE:=LuCI core runtime
LUCI_DEPENDS:=\
+rpcd \
+rpcd-mod-file \
+rpcd-mod-luci \
+rpcd-mod-ucode \
+cgi-io \
+ucode \
+ucode-mod-fs \
+ucode-mod-uci \
+ucode-mod-ubus \
+ucode-mod-math \
+ucode-mod-html \
+liblucihttp-ucode
PKG_LICENSE:=MIT
@ -26,6 +38,20 @@ define Package/luci-base/conffiles
/etc/config/ucitrack
endef
define Package/luci-base/postinst
#!/bin/sh
if [ -z "$${PKG_INSTROOT}" ] && [ -f /etc/config/uhttpd ]; then
if ! uci -q get uhttpd.main.ucode_prefix | grep -sq /cgi-bin/luci; then
uci add_list uhttpd.main.ucode_prefix='/cgi-bin/luci=/usr/share/ucode/luci/uhttpd.uc'
uci commit uhttpd
service uhttpd reload
fi
fi
exit 0
endef
include ../../luci.mk
define Host/Configure

View file

@ -1,5 +1,41 @@
#!/usr/bin/lua
require "luci.cacheloader"
require "luci.sgi.cgi"
luci.dispatcher.indexcache = "/tmp/luci-indexcache"
luci.sgi.cgi.run()
#!/usr/bin/env ucode
'use strict';
import { stdin, stdout } from 'fs';
import dispatch from 'luci.dispatcher';
import request from 'luci.http';
const input_bufsize = 4096;
let input_available = +getenv('CONTENT_LENGTH') || 0;
function read(len) {
if (input_available == 0) {
stdin.close();
return null;
}
let chunk = stdin.read(min(input_available, len ?? input_bufsize, input_bufsize));
if (chunk == null) {
input_available = 0;
stdin.close();
}
else {
input_available -= length(chunk);
}
return chunk;
}
function write(data) {
return stdout.write(data);
}
let req = request(getenv(), read, write);
dispatch(req);
req.close();

View file

@ -1,199 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.admin.index", package.seeall)
function action_logout()
local dsp = require "luci.dispatcher"
local utl = require "luci.util"
local sid = dsp.context.authsession
if sid then
utl.ubus("session", "destroy", { ubus_rpc_session = sid })
local url = dsp.build_url()
if luci.http.getenv('HTTPS') == 'on' then
luci.http.header("Set-Cookie", "sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=%s" % url)
end
luci.http.header("Set-Cookie", "sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=%s" % url)
end
luci.http.redirect(dsp.build_url())
end
function action_translations(lang)
local i18n = require "luci.i18n"
local http = require "luci.http"
local fs = require "nixio".fs
if lang and #lang > 0 then
lang = i18n.setlanguage(lang)
if lang then
local s = fs.stat("%s/base.%s.lmo" %{ i18n.i18ndir, lang })
if s then
http.header("Cache-Control", "public, max-age=31536000")
http.header("ETag", "%x-%x-%x" %{ s["ino"], s["size"], s["mtime"] })
end
end
end
http.prepare_content("application/javascript; charset=utf-8")
http.write("window.TR=")
http.write_json(i18n.dump())
end
local function ubus_reply(id, data, code, errmsg)
local reply = { jsonrpc = "2.0", id = id }
if errmsg then
reply.error = {
code = code,
message = errmsg
}
elseif type(code) == "table" then
reply.result = code
else
reply.result = { code, data }
end
return reply
end
local ubus_types = {
nil,
"array",
"object",
"string",
nil, -- INT64
"number",
nil, -- INT16,
"boolean",
"double"
}
local function ubus_access(sid, obj, fun)
local res, code = luci.util.ubus("session", "access", {
ubus_rpc_session = sid,
scope = "ubus",
object = obj,
["function"] = fun
})
return (type(res) == "table" and res.access == true)
end
local function ubus_request(req)
if type(req) ~= "table" or type(req.method) ~= "string" or req.jsonrpc ~= "2.0" or req.id == nil then
return ubus_reply(nil, nil, -32600, "Invalid request")
elseif req.method == "call" then
if type(req.params) ~= "table" or #req.params < 3 then
return ubus_reply(nil, nil, -32600, "Invalid parameters")
end
local sid, obj, fun, arg =
req.params[1], req.params[2], req.params[3], req.params[4] or {}
if type(arg) ~= "table" or arg.ubus_rpc_session ~= nil then
return ubus_reply(req.id, nil, -32602, "Invalid parameters")
end
if sid == "00000000000000000000000000000000" and luci.dispatcher.context.authsession then
sid = luci.dispatcher.context.authsession
end
if not ubus_access(sid, obj, fun) then
return ubus_reply(req.id, nil, -32002, "Access denied")
end
arg.ubus_rpc_session = sid
local res, code = luci.util.ubus(obj, fun, arg)
return ubus_reply(req.id, res, code or 0)
elseif req.method == "list" then
if req.params == nil or (type(req.params) == "table" and #req.params == 0) then
local objs = luci.util.ubus()
return ubus_reply(req.id, nil, objs)
elseif type(req.params) == "table" then
local n, rv = nil, {}
for n = 1, #req.params do
if type(req.params[n]) ~= "string" then
return ubus_reply(req.id, nil, -32602, "Invalid parameters")
end
local sig = luci.util.ubus(req.params[n])
if sig and type(sig) == "table" then
rv[req.params[n]] = {}
local m, p
for m, p in pairs(sig) do
if type(p) == "table" then
rv[req.params[n]][m] = {}
local pn, pt
for pn, pt in pairs(p) do
rv[req.params[n]][m][pn] = ubus_types[pt] or "unknown"
end
end
end
end
end
return ubus_reply(req.id, nil, rv)
else
return ubus_reply(req.id, nil, -32602, "Invalid parameters")
end
end
return ubus_reply(req.id, nil, -32601, "Method not found")
end
function action_ubus()
local parser = require "luci.jsonc".new()
luci.http.context.request:setfilehandler(function(_, s)
if not s then
return nil
end
local ok, err = parser:parse(s)
return (not err or nil)
end)
luci.http.context.request:content()
local json = parser:get()
if json == nil or type(json) ~= "table" then
luci.http.prepare_content("application/json")
luci.http.write_json(ubus_reply(nil, nil, -32700, "Parse error"))
return
end
local response
if #json == 0 then
response = ubus_request(json)
else
response = {}
local _, request
for _, request in ipairs(json) do
response[_] = ubus_request(request)
end
end
luci.http.prepare_content("application/json")
luci.http.write_json(response)
end
function action_menu()
local dsp = require "luci.dispatcher"
local http = require "luci.http"
local _, _, acls = dsp.is_authenticated({ methods = { "cookie:sysauth_https", "cookie:sysauth_http" } })
local menu = dsp.menu_json(acls or {}) or {}
http.prepare_content("application/json")
http.write_json(menu)
end

View file

@ -1,70 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2010-2019 Jo-Philipp Wich <jo@mein.io>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.admin.uci", package.seeall)
local function ubus_state_to_http(errstr)
local map = {
["Invalid command"] = 400,
["Invalid argument"] = 400,
["Method not found"] = 404,
["Entry not found"] = 404,
["No data"] = 204,
["Permission denied"] = 403,
["Timeout"] = 504,
["Not supported"] = 500,
["Unknown error"] = 500,
["Connection failed"] = 503
}
local code = map[errstr] or 200
local msg = errstr or "OK"
luci.http.status(code, msg)
if code ~= 204 then
luci.http.prepare_content("text/plain")
luci.http.write(msg)
end
end
function action_apply_rollback()
local uci = require "luci.model.uci"
local token, errstr = uci:apply(true)
if token then
luci.http.prepare_content("application/json")
luci.http.write_json({ token = token })
else
ubus_state_to_http(errstr)
end
end
function action_apply_unchecked()
local uci = require "luci.model.uci"
local _, errstr = uci:apply(false)
ubus_state_to_http(errstr)
end
function action_confirm()
local uci = require "luci.model.uci"
local token = luci.http.formvalue("token")
local _, errstr = uci:confirm(token)
ubus_state_to_http(errstr)
end
function action_revert()
local uci = require "luci.model.uci"
local changes = uci:changes()
-- Collect files to be reverted
local _, errstr, r, tbl
for r, tbl in pairs(changes) do
_, errstr = uci:revert(r)
if errstr then
break
end
end
ubus_state_to_http(errstr or "OK")
end

File diff suppressed because it is too large Load diff

View file

@ -1,220 +0,0 @@
---[[
LuCI web dispatcher.
]]
module "luci.dispatcher"
---[[
Build the URL relative to the server webroot from given virtual path.
@class function
@name build_url
@param ... Virtual path
@return Relative URL
]]
---[[
Check whether a dispatch node shall be visible
@class function
@name node_visible
@param node Dispatch node
@return Boolean indicating whether the node should be visible
]]
---[[
Return a sorted table of visible children within a given node
@class function
@name node_childs
@param node Dispatch node
@return Ordered table of child node names
]]
---[[
Send a 404 error code and render the "error404" template if available.
@class function
@name error404
@param message Custom error message (optional)
@return false
]]
---[[
Send a 500 error code and render the "error500" template if available.
@class function
@name error500
@param message Custom error message (optional)#
@return false
]]
---[[
Dispatch an HTTP request.
@class function
@name httpdispatch
@param request LuCI HTTP Request object
]]
---[[
Dispatches a LuCI virtual path.
@class function
@name dispatch
@param request Virtual path
]]
---[[
Generate the dispatching index using the native file-cache based strategy.
@class function
@name createindex
]]
---[[
Create the dispatching tree from the index.
Build the index before if it does not exist yet.
@class function
@name createtree
]]
---[[
Clone a node of the dispatching tree to another position.
@class function
@name assign
@param path Virtual path destination
@param clone Virtual path source
@param title Destination node title (optional)
@param order Destination node order value (optional)
@return Dispatching tree node
]]
---[[
Create a new dispatching node and define common parameters.
@class function
@name entry
@param path Virtual path
@param target Target function to call when dispatched.
@param title Destination node title
@param order Destination node order value (optional)
@return Dispatching tree node
]]
---[[
Fetch or create a dispatching node without setting the target module or
enabling the node.
@class function
@name get
@param ... Virtual path
@return Dispatching tree node
]]
---[[
Fetch or create a new dispatching node.
@class function
@name node
@param ... Virtual path
@return Dispatching tree node
]]
---[[
Lookup node in dispatching tree.
@class function
@name lookup
@param ... Virtual path
@return Node object, canonical url or nil if the path was not found.
]]
---[[
Alias the first (lowest order) page automatically
@class function
@name firstchild
]]
---[[
Create a redirect to another dispatching node.
@class function
@name alias
@param ... Virtual path destination
]]
---[[
Rewrite the first x path values of the request.
@class function
@name rewrite
@param n Number of path values to replace
@param ... Virtual path to replace removed path values with
]]
---[[
Create a function-call dispatching target.
@class function
@name call
@param name Target function of local controller
@param ... Additional parameters passed to the function
]]
---[[
Create a template render dispatching target.
@class function
@name template
@param name Template to be rendered
]]
---[[
Create a CBI model dispatching target.
@class function
@name cbi
@param model CBI model to be rendered
]]
---[[
Create a combined dispatching target for non argv and argv requests.
@class function
@name arcombine
@param trg1 Overview Target
@param trg2 Detail Target
]]
---[[
Create a CBI form model dispatching target.
@class function
@name form
@param model CBI form model tpo be rendered
]]
---[[
Access the luci.i18n translate() api.
@class function
@name translate
@param text Text to translate
]]
---[[
No-op function used to mark translation entries for menu labels.
This function does not actually translate the given argument but
is used by build/i18n-scan.pl to find translatable entries.
@class function
@name _
]]

View file

@ -1,100 +0,0 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local util = require "luci.util"
local config = require "luci.config"
local tparser = require "luci.template.parser"
local tostring, pairs, loadstring = tostring, pairs, loadstring
local setmetatable, loadfile = setmetatable, loadfile
local getfenv, setfenv, rawget = getfenv, setfenv, rawget
local assert, type, error = assert, type, error
--- LuCI template library.
module "luci.template"
config.template = config.template or {}
viewdir = config.template.viewdir or util.libpath() .. "/view"
-- Define the namespace for template modules
context = util.threadlocal()
--- Render a certain template.
-- @param name Template name
-- @param scope Scope to assign to template (optional)
function render(name, scope)
return Template(name):render(scope or getfenv(2))
end
--- Render a template from a string.
-- @param template Template string
-- @param scope Scope to assign to template (optional)
function render_string(template, scope)
return Template(nil, template):render(scope or getfenv(2))
end
-- Template class
Template = util.class()
-- Shared template cache to store templates in to avoid unnecessary reloading
Template.cache = setmetatable({}, {__mode = "v"})
-- Constructor - Reads and compiles the template on-demand
function Template.__init__(self, name, template)
if name then
self.template = self.cache[name]
self.name = name
else
self.name = "[string]"
end
-- Create a new namespace for this template
self.viewns = context.viewns
-- If we have a cached template, skip compiling and loading
if not self.template then
-- Compile template
local err
local sourcefile
if name then
sourcefile = viewdir .. "/" .. name .. ".htm"
self.template, _, err = tparser.parse(sourcefile)
else
sourcefile = "[string]"
self.template, _, err = tparser.parse_string(template)
end
-- If we have no valid template throw error, otherwise cache the template
if not self.template then
error("Failed to load template '" .. self.name .. "'.\n" ..
"Error while parsing template '" .. sourcefile .. "':\n" ..
(err or "Unknown syntax error"))
elseif name then
self.cache[name] = self.template
end
end
end
-- Renders a template
function Template.render(self, scope)
scope = scope or getfenv(2)
-- Put our predefined objects in the scope of the template
setfenv(self.template, setmetatable({}, {__index =
function(tbl, key)
return rawget(tbl, key) or self.viewns[key] or scope[key]
end}))
-- Now finally render the thing
local stat, err = util.copcall(self.template)
if not stat then
error("Failed to execute template '" .. self.name .. "'.\n" ..
"A runtime error occurred: " .. tostring(err or "(nil)"))
end
end

View file

@ -1,24 +0,0 @@
<%#
Copyright 2015 Jo-Philipp Wich <jow@openwrt.org>
Licensed to the public under the Apache License 2.0.
-%>
<%+header%>
<h2 name="content"><%:Form token mismatch%></h2>
<br />
<p class="alert-message"><%:The submitted security token is invalid or already expired!%></p>
<p><%:
In order to prevent unauthorized access to the system, your request has
been blocked. Click "Continue »" below to return to the previous page.
%></p>
<hr />
<p class="right">
<strong><a href="#" onclick="window.history.back();">Continue »</a></strong>
</p>
<%+footer%>

View file

@ -1,12 +0,0 @@
<%#
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.
-%>
<%+header%>
<h2 name="content">404 <%:Not Found%></h2>
<p><%:Sorry, the object you requested was not found.%></p>
<p><%=message%></p>
<tt><%:Unable to dispatch%>: <%=url(unpack(luci.dispatcher.context.request))%></tt>
<%+footer%>

View file

@ -1,11 +0,0 @@
<%#
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.
-%>
<%+header%>
<h2 name="content">500 <%:Internal Server Error%></h2>
<p><%:Sorry, the server encountered an unexpected error.%></p>
<pre class="error500"><%=message%></pre>
<%+footer%>

View file

@ -1,27 +0,0 @@
<%#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
<%
local is_rollback_pending, rollback_time_remaining, rollback_session, rollback_token = luci.model.uci:rollback_pending()
if is_rollback_pending or trigger_apply or trigger_revert then
%>
<script type="text/javascript">
document.addEventListener("luci-loaded", function() {
<% if trigger_apply then -%>
L.ui.changes.apply(true);
<%- elseif trigger_revert then -%>
L.ui.changes.revert();
<%- else -%>
L.ui.changes.confirm(true, Date.now() + <%=rollback_time_remaining%> * 1000, <%=luci.http.write_json(rollback_token)%>);
<%- end %>
});
</script>
<%
end
include("themes/" .. theme .. "/footer")
%>

View file

@ -1,38 +0,0 @@
<%#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
<%
if not luci.dispatcher.context.template_header_sent then
include("themes/" .. theme .. "/header")
luci.dispatcher.context.template_header_sent = true
end
local applyconf = luci.config and luci.config.apply
%>
<script type="text/javascript" src="<%=resource%>/promis.min.js"></script>
<script type="text/javascript" src="<%=resource%>/luci.js"></script>
<script type="text/javascript">
L = new LuCI(<%= luci.http.write_json({
token = token,
media = media,
resource = resource,
scriptname = luci.http.getenv("SCRIPT_NAME"),
pathinfo = luci.http.getenv("PATH_INFO"),
documentroot = luci.http.getenv("DOCUMENT_ROOT"),
requestpath = luci.dispatcher.context.requestpath,
dispatchpath = luci.dispatcher.context.path,
pollinterval = luci.config.main.pollinterval or 5,
ubuspath = luci.config.main.ubuspath or '/ubus/',
sessionid = luci.dispatcher.context.authsession,
nodespec = luci.dispatcher.context.dispatched,
apply_rollback = math.max(applyconf and applyconf.rollback or 90, 90),
apply_holdoff = math.max(applyconf and applyconf.holdoff or 4, 1),
apply_timeout = math.max(applyconf and applyconf.timeout or 5, 1),
apply_display = math.max(applyconf and applyconf.display or 1.5, 1),
rollback_token = rollback_token
}) %>);
</script>

View file

@ -1,75 +0,0 @@
<%#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org>
Licensed to the public under the Apache License 2.0.
-%>
<%+header%>
<form method="post" action="<%=pcdata(FULL_REQUEST_URI)%>">
<%- if fuser then %>
<div class="alert-message warning">
<p><%:Invalid username and/or password! Please try again.%></p>
</div>
<% end -%>
<div class="cbi-map">
<h2 name="content"><%:Authorization Required%></h2>
<div class="cbi-map-descr">
<%:Please enter your username and password.%>
</div>
<div class="cbi-section"><div class="cbi-section-node">
<div class="cbi-value">
<label class="cbi-value-title" for="luci_username"><%:Username%></label>
<div class="cbi-value-field">
<input class="cbi-input-text" type="text" name="luci_username" id="luci_username" autocomplete="username" value="<%=duser%>" />
</div>
</div>
<div class="cbi-value cbi-value-last">
<label class="cbi-value-title" for="luci_password"><%:Password%></label>
<div class="cbi-value-field">
<input class="cbi-input-text" type="password" name="luci_password" id="luci_password" autocomplete="current-password"/>
</div>
</div>
</div></div>
</div>
<div class="cbi-page-actions">
<input type="submit" value="<%:Login%>" class="btn cbi-button cbi-button-apply" />
<input type="reset" value="<%:Reset%>" class="btn cbi-button cbi-button-reset" />
</div>
</form>
<script type="text/javascript">//<![CDATA[
var input = document.getElementsByName('luci_password')[0];
if (input)
input.focus();
//]]></script>
<%
local uci = require "luci.model.uci".cursor()
local fs = require "nixio.fs"
local https_key = uci:get("uhttpd", "main", "key")
local https_port = uci:get("uhttpd", "main", "listen_https")
if type(https_port) == "table" then
https_port = https_port[1]
end
if https_port and fs.access(https_key) then
https_port = https_port:match("(%d+)$")
%>
<script type="text/javascript">//<![CDATA[
if (document.location.protocol != 'https:') {
var url = 'https://' + window.location.hostname + ':' + '<%=https_port%>' + window.location.pathname;
var img=new Image;
img.onload=function(){window.location = url};
img.src='https://' + window.location.hostname + ':' + '<%=https_port%>' + '<%=resource%>/icons/loading.gif?' + Math.random();
setTimeout(function(){
img.src=''
}, 5000);
}
//]]></script>
<% end %>
<%+footer%>

View file

@ -1,12 +0,0 @@
<%+header%>
<div id="view">
<div class="spinning"><%:Loading view…%></div>
<script type="text/javascript">
L.require('ui').then(function(ui) {
ui.instantiateView('<%=view%>');
});
</script>
</div>
<%+footer%>

View file

@ -1,682 +0,0 @@
#!/usr/bin/env lua
local json = require "luci.jsonc"
local fs = require "nixio.fs"
local function readfile(path)
local s = fs.readfile(path)
return s and (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
local methods = {
getInitList = {
args = { name = "name" },
call = function(args)
local sys = require "luci.sys"
local _, name, scripts = nil, nil, {}
for _, name in ipairs(args.name and { args.name } or sys.init.names()) do
local index = sys.init.index(name)
if index then
scripts[name] = { index = index, enabled = sys.init.enabled(name) }
else
return { error = "No such init script" }
end
end
return scripts
end
},
setInitAction = {
args = { name = "name", action = "action" },
call = function(args)
local sys = require "luci.sys"
if type(sys.init[args.action]) ~= "function" then
return { error = "Invalid action" }
end
return { result = sys.init[args.action](args.name) }
end
},
getLocaltime = {
call = function(args)
return { result = os.time() }
end
},
setLocaltime = {
args = { localtime = 0 },
call = function(args)
local sys = require "luci.sys"
local date = os.date("*t", args.localtime)
if date then
sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec })
sys.call("/etc/init.d/sysfixtime restart >/dev/null")
end
return { result = args.localtime }
end
},
getTimezones = {
call = function(args)
local util = require "luci.util"
local zones = require "luci.sys.zoneinfo"
local tz = readfile("/etc/TZ")
local res = util.ubus("uci", "get", {
config = "system",
section = "@system[0]",
option = "zonename"
})
local result = {}
local _, zone
for _, zone in ipairs(zones.TZ) do
result[zone[1]] = {
tzstring = zone[2],
active = (res and res.value == zone[1]) and true or nil
}
end
return result
end
},
getLEDs = {
call = function()
local iter = fs.dir("/sys/class/leds")
local result = { }
if iter then
local led
for led in iter do
local m, s
result[led] = { triggers = {} }
s = readfile("/sys/class/leds/"..led.."/trigger")
for s in (s or ""):gmatch("%S+") do
m = s:match("^%[(.+)%]$")
result[led].triggers[#result[led].triggers+1] = m or s
result[led].active_trigger = m or result[led].active_trigger
end
s = readfile("/sys/class/leds/"..led.."/brightness")
if s then
result[led].brightness = tonumber(s)
end
s = readfile("/sys/class/leds/"..led.."/max_brightness")
if s then
result[led].max_brightness = tonumber(s)
end
end
end
return result
end
},
getUSBDevices = {
call = function()
local fs = require "nixio.fs"
local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer")
local result = { }
if iter then
result.devices = {}
local p
for p in iter do
local id = p:match("/([^/]+)/manufacturer$")
result.devices[#result.devices+1] = {
id = id,
vid = readfile("/sys/bus/usb/devices/"..id.."/idVendor"),
pid = readfile("/sys/bus/usb/devices/"..id.."/idProduct"),
vendor = readfile("/sys/bus/usb/devices/"..id.."/manufacturer"),
product = readfile("/sys/bus/usb/devices/"..id.."/product"),
speed = tonumber((readfile("/sys/bus/usb/devices/"..id.."/product")))
}
end
end
iter = fs.glob("/sys/bus/usb/devices/*/*-port[0-9]*")
if iter then
result.ports = {}
local p
for p in iter do
local port = p:match("([^/]+)$")
local link = fs.readlink(p.."/device")
result.ports[#result.ports+1] = {
port = port,
device = link and fs.basename(link)
}
end
end
return result
end
},
getConntrackHelpers = {
call = function()
local ok, fd = pcall(io.open, "/usr/share/fw3/helpers.conf", "r")
local rv = {}
if not (ok and fd) then
ok, fd = pcall(io.open, "/usr/share/firewall4/helpers", "r")
end
if ok and fd then
local entry
while true do
local line = fd:read("*l")
if not line then
break
end
if line:match("^%s*config%s") then
if entry then
rv[#rv+1] = entry
end
entry = {}
else
local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$")
if opt and val then
opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
entry[opt] = val
end
end
end
if entry then
rv[#rv+1] = entry
end
fd:close()
end
return { result = rv }
end
},
getFeatures = {
call = function()
local fs = require "nixio.fs"
local rv = {}
local ok, fd
rv.firewall = fs.access("/sbin/fw3")
rv.firewall4 = fs.access("/sbin/fw4")
rv.opkg = fs.access("/bin/opkg")
rv.offloading = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") or fs.access("/sys/module/nft_flow_offload/refcnt")
rv.br2684ctl = fs.access("/usr/sbin/br2684ctl")
rv.swconfig = fs.access("/sbin/swconfig")
rv.odhcpd = fs.access("/usr/sbin/odhcpd")
rv.zram = fs.access("/sys/class/zram-control")
rv.sysntpd = fs.readlink("/usr/sbin/ntpd") and true
rv.ipv6 = fs.access("/proc/net/ipv6_route")
rv.dropbear = fs.access("/usr/sbin/dropbear")
rv.cabundle = fs.access("/etc/ssl/certs/ca-certificates.crt")
rv.relayd = fs.access("/usr/sbin/relayd")
local wifi_features = { "eap", "11n", "11ac", "11r", "acs", "sae", "owe", "suiteb192", "wep", "wps" }
if fs.access("/usr/sbin/hostapd") then
rv.hostapd = { cli = fs.access("/usr/sbin/hostapd_cli") }
local _, feature
for _, feature in ipairs(wifi_features) do
rv.hostapd[feature] =
(os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0)
end
end
if fs.access("/usr/sbin/wpa_supplicant") then
rv.wpasupplicant = { cli = fs.access("/usr/sbin/wpa_cli") }
local _, feature
for _, feature in ipairs(wifi_features) do
rv.wpasupplicant[feature] =
(os.execute(string.format("/usr/sbin/wpa_supplicant -v%s >/dev/null 2>/dev/null", feature)) == 0)
end
end
ok, fd = pcall(io.popen, "dnsmasq --version 2>/dev/null")
if ok then
rv.dnsmasq = {}
while true do
local line = fd:read("*l")
if not line then
break
end
local opts = line:match("^Compile time options: (.+)$")
if opts then
local opt
for opt in opts:gmatch("%S+") do
local no = opt:match("^no%-(%S+)$")
rv.dnsmasq[string.lower(no or opt)] = not no
end
break
end
end
fd:close()
end
ok, fd = pcall(io.popen, "ipset --help 2>/dev/null")
if ok then
rv.ipset = {}
local sets = false
while true do
local line = fd:read("*l")
if not line then
break
elseif line:match("^Supported set types:") then
sets = true
elseif sets then
local set, ver = line:match("^%s+(%S+)%s+(%d+)")
if set and not rv.ipset[set] then
rv.ipset[set] = tonumber(ver)
end
end
end
fd:close()
end
return rv
end
},
getSwconfigFeatures = {
args = { switch = "switch0" },
call = function(args)
local util = require "luci.util"
-- Parse some common switch properties from swconfig help output.
local swc, err = io.popen("swconfig dev %s help 2>/dev/null" % util.shellquote(args.switch))
if swc then
local is_port_attr = false
local is_vlan_attr = false
local rv = {}
while true do
local line = swc:read("*l")
if not line then break end
if line:match("^%s+%-%-vlan") then
is_vlan_attr = true
elseif line:match("^%s+%-%-port") then
is_vlan_attr = false
is_port_attr = true
elseif line:match("cpu @") then
rv.switch_title = line:match("^switch%d: %w+%((.-)%)")
rv.num_vlans = tonumber(line:match("vlans: (%d+)")) or 16
rv.min_vid = 1
elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then
if is_vlan_attr then rv.vid_option = line:match(": (%w+)") end
elseif line:match(": enable_vlan4k") then
rv.vlan4k_option = "enable_vlan4k"
elseif line:match(": enable_vlan") then
rv.vlan_option = "enable_vlan"
elseif line:match(": enable_learning") then
rv.learning_option = "enable_learning"
elseif line:match(": enable_mirror_rx") then
rv.mirror_option = "enable_mirror_rx"
elseif line:match(": max_length") then
rv.jumbo_option = "max_length"
end
end
swc:close()
if not next(rv) then
return { error = "No such switch" }
end
return rv
else
return { error = err }
end
end
},
getSwconfigPortState = {
args = { switch = "switch0" },
call = function(args)
local util = require "luci.util"
local swc, err = io.popen("swconfig dev %s show 2>/dev/null" % util.shellquote(args.switch))
if swc then
local ports = { }
while true do
local line = swc:read("*l")
if not line or (line:match("^VLAN %d+:") and #ports > 0) then
break
end
local pnum = line:match("^Port (%d+):$")
if pnum then
port = {
port = tonumber(pnum),
duplex = false,
speed = 0,
link = false,
auto = false,
rxflow = false,
txflow = false
}
ports[#ports+1] = port
end
if port then
local m
if line:match("full[%- ]duplex") then
port.duplex = true
end
m = line:match(" speed:(%d+)")
if m then
port.speed = tonumber(m)
end
m = line:match("(%d+) Mbps")
if m and port.speed == 0 then
port.speed = tonumber(m)
end
m = line:match("link: (%d+)")
if m and port.speed == 0 then
port.speed = tonumber(m)
end
if line:match("link: ?up") or line:match("status: ?up") then
port.link = true
end
if line:match("auto%-negotiate") or line:match("link:.-auto") then
port.auto = true
end
if line:match("link:.-rxflow") then
port.rxflow = true
end
if line:match("link:.-txflow") then
port.txflow = true
end
end
end
swc:close()
if not next(ports) then
return { error = "No such switch" }
end
return { result = ports }
else
return { error = err }
end
end
},
setPassword = {
args = { username = "root", password = "password" },
call = function(args)
local util = require "luci.util"
return {
result = (os.execute("(echo %s; sleep 1; echo %s) | /bin/busybox passwd %s >/dev/null 2>&1" %{
luci.util.shellquote(args.password),
luci.util.shellquote(args.password),
luci.util.shellquote(args.username)
}) == 0)
}
end
},
getBlockDevices = {
call = function()
local fs = require "nixio.fs"
local block = io.popen("/sbin/block info", "r")
if block then
local rv = {}
while true do
local ln = block:read("*l")
if not ln then
break
end
local dev = ln:match("^/dev/(.-):")
if dev then
local s = tonumber((fs.readfile("/sys/class/block/" .. dev .."/size")))
local e = {
dev = "/dev/" .. dev,
size = s and s * 512
}
local key, val = { }
for key, val in ln:gmatch([[(%w+)="(.-)"]]) do
e[key:lower()] = val
end
rv[dev] = e
end
end
block:close()
return rv
else
return { error = "Unable to execute block utility" }
end
end
},
setBlockDetect = {
call = function()
return { result = (os.execute("/sbin/block detect > /etc/config/fstab") == 0) }
end
},
getMountPoints = {
call = function()
local fs = require "nixio.fs"
local fd, err = io.open("/proc/mounts", "r")
if fd then
local rv = {}
while true do
local ln = fd:read("*l")
if not ln then
break
end
local device, mount, fstype, options, freq, pass = ln:match("^(%S*) (%S*) (%S*) (%S*) (%d+) (%d+)$")
if device and mount then
device = device:gsub("\\(%d+)", function(n) return string.char(tonumber(n, 8)) end)
mount = mount:gsub("\\(%d+)", function(n) return string.char(tonumber(n, 8)) end)
local stat = fs.statvfs(mount)
if stat and stat.blocks > 0 then
rv[#rv+1] = {
device = device,
mount = mount,
size = stat.bsize * stat.blocks,
avail = stat.bsize * stat.bavail,
free = stat.bsize * stat.bfree
}
end
end
end
fd:close()
return { result = rv }
else
return { error = err }
end
end
},
getRealtimeStats = {
args = { mode = "interface", device = "eth0" },
call = function(args)
local util = require "luci.util"
local flags
if args.mode == "interface" then
flags = "-i %s" % util.shellquote(args.device)
elseif args.mode == "wireless" then
flags = "-r %s" % util.shellquote(args.device)
elseif args.mode == "conntrack" then
flags = "-c"
elseif args.mode == "load" then
flags = "-l"
else
return { error = "Invalid mode" }
end
local fd, err = io.popen("luci-bwc %s" % flags, "r")
if fd then
local parse = json.new()
local done
parse:parse("[")
while true do
local ln = fd:read("*l")
if not ln then
break
end
done, err = parse:parse((ln:gsub("%d+", "%1.0")))
if done then
err = "Unexpected JSON data"
end
if err then
break
end
end
fd:close()
done, err = parse:parse("]")
if err then
return { error = err }
elseif not done then
return { error = "Incomplete JSON data" }
else
return { result = parse:get() }
end
else
return { error = err }
end
end
},
getConntrackList = {
call = function()
local sys = require "luci.sys"
return { result = sys.net.conntrack() }
end
},
getProcessList = {
call = function()
local sys = require "luci.sys"
local res = {}
for _, v in pairs(sys.process.list()) do
res[#res + 1] = v
end
return { result = res }
end
}
}
local function parseInput()
local parse = json.new()
local done, err
while true do
local chunk = io.read(4096)
if not chunk then
break
elseif not done and not err then
done, err = parse:parse(chunk)
end
end
if not done then
print(json.stringify({ error = err or "Incomplete input" }))
os.exit(1)
end
return parse:get()
end
local function validateArgs(func, uargs)
local method = methods[func]
if not method then
print(json.stringify({ error = "Method not found" }))
os.exit(1)
end
if type(uargs) ~= "table" then
print(json.stringify({ error = "Invalid arguments" }))
os.exit(1)
end
uargs.ubus_rpc_session = nil
local k, v
local margs = method.args or {}
for k, v in pairs(uargs) do
if margs[k] == nil or
(v ~= nil and type(v) ~= type(margs[k]))
then
print(json.stringify({ error = "Invalid arguments" }))
os.exit(1)
end
end
return method
end
if arg[1] == "list" then
local _, method, rv = nil, nil, {}
for _, method in pairs(methods) do rv[_] = method.args or {} end
print((json.stringify(rv):gsub(":%[%]", ":{}")))
elseif arg[1] == "call" then
local args = parseInput()
local method = validateArgs(arg[2], args)
local result, code = method.call(args)
print((json.stringify(result):gsub("^%[%]$", "{}")))
os.exit(code or 0)
end

View file

@ -61,7 +61,7 @@
"admin/translations/*": {
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.index",
"function": "action_translations"
},
@ -70,7 +70,7 @@
"admin/ubus/*": {
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.index",
"function": "action_ubus"
},
@ -81,7 +81,7 @@
"title": "Logout",
"order": 999,
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.index",
"function": "action_logout"
},
@ -99,7 +99,7 @@
"admin/uci/revert": {
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.uci",
"function": "action_revert",
"post": true
@ -109,7 +109,7 @@
"admin/uci/apply_rollback": {
"cors": true,
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.uci",
"function": "action_apply_rollback",
"post": true
@ -122,7 +122,7 @@
"admin/uci/apply_unchecked": {
"cors": true,
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.uci",
"function": "action_apply_unchecked",
"post": true
@ -135,7 +135,7 @@
"admin/uci/confirm": {
"cors": true,
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.uci",
"function": "action_confirm"
},
@ -144,7 +144,7 @@
"admin/menu": {
"action": {
"type": "call",
"type": "function",
"module": "luci.controller.admin.index",
"function": "action_menu"
},

View file

@ -4,29 +4,31 @@
contrib/lemon: contrib/lemon.c contrib/lempar.c
cc -o contrib/lemon $<
plural_formula.c: plural_formula.y contrib/lemon
lib/plural_formula.c: lib/plural_formula.y contrib/lemon
./contrib/lemon -q $<
template_lmo.c: plural_formula.c
lib/lmo.c: lib/plural_formula.c
core.so: lib/luci.o lib/lmo.o lib/plural_formula.o
$(CC) $(LDFLAGS) -shared -o $@ $^
version.uc:
echo "export const revision = '$(LUCI_VERSION)', branch = '$(LUCI_GITBRANCH)';" > $@
clean:
rm -f contrib/lemon po2lmo parser.so version.lua plural_formula.c plural_formula.h *.o
rm -f contrib/lemon lib/*.o lib/plural_formula.c lib/plural_formula.h core.so version.uc
jsmin: jsmin.o
$(CC) $(LDFLAGS) -o $@ $^
po2lmo: po2lmo.o template_lmo.o plural_formula.o
po2lmo: po2lmo.o lib/lmo.o lib/plural_formula.o
$(CC) $(LDFLAGS) -o $@ $^
parser.so: template_parser.o template_utils.o template_lmo.o template_lualib.o plural_formula.o
$(CC) $(LDFLAGS) -shared -o $@ $^
version.lua:
./mkversion.sh $@ $(LUCI_VERSION) "$(LUCI_GITBRANCH)"
compile: parser.so version.lua
compile: core.so version.uc
install: compile
mkdir -p $(DESTDIR)/usr/lib/lua/luci/template
cp parser.so $(DESTDIR)/usr/lib/lua/luci/template/parser.so
cp version.lua $(DESTDIR)/usr/lib/lua/luci/version.lua
mkdir -p $(DESTDIR)/usr/lib/ucode/luci
cp core.so $(DESTDIR)/usr/lib/ucode/luci/core.so
mkdir -p $(DESTDIR)/usr/share/ucode/luci
cp version.uc $(DESTDIR)/usr/share/ucode/luci/version.uc

View file

@ -16,7 +16,7 @@
* limitations under the License.
*/
#include "template_lmo.h"
#include "lib/lmo.h"
static void die(const char *msg)
{
@ -169,8 +169,11 @@ static void print_msg(struct msg *msg, FILE *out)
else
snprintf(key, sizeof(key), "%s", msg->id);
key_id = sfh_hash(key, strlen(key));
val_id = sfh_hash(msg->val[i], strlen(msg->val[i]));
len = strlen(key);
key_id = sfh_hash(key, len, len);
len = strlen(msg->val[i]);
val_id = sfh_hash(msg->val[i], len, len);
if (key_id != val_id) {
n_entries++;

View file

@ -47,7 +47,6 @@ const Class = {
if (!this.L) {
this.L = this.env.dispatcher.load_luabridge().create();
this.L.set('L', proto({ write: print }, this.env));
this.L.eval('package.path = "/usr/lib/lua/luci/ucodebridge/?.lua;" .. package.path');
this.L.invoke('require', 'luci.ucodebridge');
this.env.lua_active = true;

View file

@ -12,7 +12,7 @@ LUCI_TYPE:=mod
LUCI_BASENAME:=compat
LUCI_TITLE:=LuCI compatibility libraries
LUCI_DEPENDS:=+luci-base
LUCI_DEPENDS:=+luci-lua-runtime
include ../../luci.mk

View file

@ -0,0 +1,27 @@
#
# Copyright (C) 2022 Jo-Philipp Wich <jo@mein.io>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-lua-runtime
LUCI_TYPE:=mod
LUCI_BASENAME:=lua-runtime
LUCI_TITLE:=LuCI Lua runtime libraries
LUCI_DEPENDS:= \
+luci-base \
+lua \
+luci-lib-nixio \
+luci-lib-ip \
+luci-lib-jsonc \
+libubus-lua \
+liblucihttp-lua \
+ucode-mod-lua
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,26 @@
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -DNDEBUG -c -o $@ $<
contrib/lemon: contrib/lemon.c contrib/lempar.c
cc -o contrib/lemon $<
plural_formula.c: plural_formula.y contrib/lemon
./contrib/lemon -q $<
template_lmo.c: plural_formula.c
clean:
rm -f contrib/lemon parser.so plural_formula.c plural_formula.h *.o
parser.so: template_parser.o template_utils.o template_lmo.o template_lualib.o plural_formula.o
$(CC) $(LDFLAGS) -shared -o $@ $^
version.lua:
./mkversion.sh $@ $(LUCI_VERSION) "$(LUCI_GITBRANCH)"
compile: parser.so version.lua
install: compile
mkdir -p $(DESTDIR)/usr/lib/lua/luci/template
cp parser.so $(DESTDIR)/usr/lib/lua/luci/template/parser.so
cp version.lua $(DESTDIR)/usr/lib/lua/luci/version.lua