diff options
Diffstat (limited to 'modules/luci-base')
64 files changed, 3862 insertions, 7264 deletions
diff --git a/modules/luci-base/Makefile b/modules/luci-base/Makefile index 6cb2b64092..f85f753320 100644 --- a/modules/luci-base/Makefile +++ b/modules/luci-base/Makefile @@ -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 diff --git a/modules/luci-base/htdocs/cgi-bin/luci b/modules/luci-base/htdocs/cgi-bin/luci index c5c9847346..442e427d41 100755 --- a/modules/luci-base/htdocs/cgi-bin/luci +++ b/modules/luci-base/htdocs/cgi-bin/luci @@ -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(); diff --git a/modules/luci-base/luasrc/cacheloader.lua b/modules/luci-base/luasrc/cacheloader.lua deleted file mode 100644 index 7ef971df8d..0000000000 --- a/modules/luci-base/luasrc/cacheloader.lua +++ /dev/null @@ -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. - -local config = require "luci.config" -local ccache = require "luci.ccache" - -module "luci.cacheloader" - -if config.ccache and config.ccache.enable == "1" then - ccache.cache_ondemand() -end diff --git a/modules/luci-base/luasrc/ccache.lua b/modules/luci-base/luasrc/ccache.lua deleted file mode 100644 index d3be7cba6c..0000000000 --- a/modules/luci-base/luasrc/ccache.lua +++ /dev/null @@ -1,76 +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. - -local io = require "io" -local fs = require "nixio.fs" -local util = require "luci.util" -local nixio = require "nixio" -local debug = require "debug" -local string = require "string" -local package = require "package" - -local type, loadfile = type, loadfile - - -module "luci.ccache" - -function cache_ondemand(...) - if debug.getinfo(1, 'S').source ~= "=?" then - cache_enable(...) - end -end - -function cache_enable(cachepath, mode) - cachepath = cachepath or "/tmp/luci-modulecache" - mode = mode or "r--r--r--" - - local loader = package.loaders[2] - local uid = nixio.getuid() - - if not fs.stat(cachepath) then - fs.mkdir(cachepath) - end - - local function _encode_filename(name) - local encoded = "" - for i=1, #name do - encoded = encoded .. ("%2X" % string.byte(name, i)) - end - return encoded - end - - local function _load_sane(file) - local stat = fs.stat(file) - if stat and stat.uid == uid and stat.modestr == mode then - return loadfile(file) - end - end - - local function _write_sane(file, func) - if nixio.getuid() == uid then - local fp = io.open(file, "w") - if fp then - fp:write(util.get_bytecode(func)) - fp:close() - fs.chmod(file, mode) - end - end - end - - package.loaders[2] = function(mod) - local encoded = cachepath .. "/" .. _encode_filename(mod) - local modcons = _load_sane(encoded) - - if modcons then - return modcons - end - - -- No cachefile - modcons = loader(mod) - if type(modcons) == "function" then - _write_sane(encoded, modcons) - end - return modcons - end -end diff --git a/modules/luci-base/luasrc/config.lua b/modules/luci-base/luasrc/config.lua deleted file mode 100644 index d01153f4f5..0000000000 --- a/modules/luci-base/luasrc/config.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -module("luci.config", - function(m) - if pcall(require, "luci.model.uci") then - local config = util.threadlocal() - setmetatable(m, { - __index = function(tbl, key) - if not config[key] then - config[key] = luci.model.uci.cursor():get_all("luci", key) - end - return config[key] - end - }) - end - end) diff --git a/modules/luci-base/luasrc/controller/admin/index.lua b/modules/luci-base/luasrc/controller/admin/index.lua deleted file mode 100644 index 8f9b481cce..0000000000 --- a/modules/luci-base/luasrc/controller/admin/index.lua +++ /dev/null @@ -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 diff --git a/modules/luci-base/luasrc/controller/admin/uci.lua b/modules/luci-base/luasrc/controller/admin/uci.lua deleted file mode 100644 index 7aad10d58a..0000000000 --- a/modules/luci-base/luasrc/controller/admin/uci.lua +++ /dev/null @@ -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 diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua deleted file mode 100644 index a3726fb1c1..0000000000 --- a/modules/luci-base/luasrc/dispatcher.lua +++ /dev/null @@ -1,1564 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local sys = require "luci.sys" -local util = require "luci.util" -local xml = require "luci.xml" -local http = require "luci.http" -local nixio = require "nixio", require "nixio.util" - -module("luci.dispatcher", package.seeall) -context = util.threadlocal() -uci = require "luci.model.uci" -i18n = require "luci.i18n" -_M.fs = fs - --- Index table -local index = nil - -local function check_fs_depends(spec) - local fs = require "nixio.fs" - - for path, kind in pairs(spec) do - if kind == "directory" then - local empty = true - for entry in (fs.dir(path) or function() end) do - empty = false - break - end - if empty then - return false - end - elseif kind == "executable" then - if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then - return false - end - elseif kind == "file" then - if fs.stat(path, "type") ~= "reg" then - return false - end - elseif kind == "absent" then - if fs.stat(path, "type") then - return false - end - end - end - - return true -end - -local function check_uci_depends_options(conf, s, opts) - local uci = require "luci.model.uci" - - if type(opts) == "string" then - return (s[".type"] == opts) - elseif opts == true then - for option, value in pairs(s) do - if option:byte(1) ~= 46 then - return true - end - end - elseif type(opts) == "table" then - for option, value in pairs(opts) do - local sval = s[option] - if type(sval) == "table" then - local found = false - for _, v in ipairs(sval) do - if v == value then - found = true - break - end - end - if not found then - return false - end - elseif value == true then - if sval == nil then - return false - end - else - if sval ~= value then - return false - end - end - end - end - - return true -end - -local function check_uci_depends_section(conf, sect) - local uci = require "luci.model.uci" - - for section, options in pairs(sect) do - local stype = section:match("^@([A-Za-z0-9_%-]+)$") - if stype then - local found = false - uci:foreach(conf, stype, function(s) - if check_uci_depends_options(conf, s, options) then - found = true - return false - end - end) - if not found then - return false - end - else - local s = uci:get_all(conf, section) - if not s or not check_uci_depends_options(conf, s, options) then - return false - end - end - end - - return true -end - -local function check_uci_depends(conf) - local uci = require "luci.model.uci" - - for config, values in pairs(conf) do - if values == true then - local found = false - uci:foreach(config, nil, function(s) - found = true - return false - end) - if not found then - return false - end - elseif type(values) == "table" then - if not check_uci_depends_section(config, values) then - return false - end - end - end - - return true -end - -local function check_acl_depends(require_groups, groups) - if type(require_groups) == "table" and #require_groups > 0 then - local writable = false - - for _, group in ipairs(require_groups) do - local read = false - local write = false - if type(groups) == "table" and type(groups[group]) == "table" then - for _, perm in ipairs(groups[group]) do - if perm == "read" then - read = true - elseif perm == "write" then - write = true - end - end - end - if not read and not write then - return nil - elseif write then - writable = true - end - end - - return writable - end - - return true -end - -local function check_depends(spec) - if type(spec.depends) ~= "table" then - return true - end - - if type(spec.depends.fs) == "table" then - local satisfied = false - local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs } - for _, alternative in ipairs(alternatives) do - if check_fs_depends(alternative) then - satisfied = true - break - end - end - if not satisfied then - return false - end - end - - if type(spec.depends.uci) == "table" then - local satisfied = false - local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci } - for _, alternative in ipairs(alternatives) do - if check_uci_depends(alternative) then - satisfied = true - break - end - end - if not satisfied then - return false - end - end - - return true -end - -local function target_to_json(target, module) - local action - - if target.type == "call" then - action = { - ["type"] = "call", - ["module"] = module, - ["function"] = target.name, - ["parameters"] = target.argv - } - elseif target.type == "view" then - action = { - ["type"] = "view", - ["path"] = target.view - } - elseif target.type == "template" then - action = { - ["type"] = "template", - ["path"] = target.view - } - elseif target.type == "cbi" then - action = { - ["type"] = "cbi", - ["path"] = target.model, - ["config"] = target.config - } - elseif target.type == "form" then - action = { - ["type"] = "form", - ["path"] = target.model - } - elseif target.type == "firstchild" then - action = { - ["type"] = "firstchild" - } - elseif target.type == "firstnode" then - action = { - ["type"] = "firstchild", - ["recurse"] = true - } - elseif target.type == "arcombine" then - if type(target.targets) == "table" then - action = { - ["type"] = "arcombine", - ["targets"] = { - target_to_json(target.targets[1], module), - target_to_json(target.targets[2], module) - } - } - end - elseif target.type == "alias" then - action = { - ["type"] = "alias", - ["path"] = table.concat(target.req, "/") - } - elseif target.type == "rewrite" then - action = { - ["type"] = "rewrite", - ["path"] = table.concat(target.req, "/"), - ["remove"] = target.n - } - end - - if target.post and action then - action.post = target.post - end - - return action -end - -local function tree_to_json(node, json) - local fs = require "nixio.fs" - local util = require "luci.util" - - if type(node.nodes) == "table" then - for subname, subnode in pairs(node.nodes) do - local spec = { - title = xml.striptags(subnode.title), - order = subnode.order - } - - if subnode.leaf then - spec.wildcard = true - end - - if subnode.cors then - spec.cors = true - end - - if subnode.setuser then - spec.setuser = subnode.setuser - end - - if subnode.setgroup then - spec.setgroup = subnode.setgroup - end - - if type(subnode.target) == "table" then - spec.action = target_to_json(subnode.target, subnode.module) - end - - if type(subnode.file_depends) == "table" then - for _, v in ipairs(subnode.file_depends) do - spec.depends = spec.depends or {} - spec.depends.fs = spec.depends.fs or {} - - local ft = fs.stat(v, "type") - if ft == "dir" then - spec.depends.fs[v] = "directory" - elseif v:match("/s?bin/") then - spec.depends.fs[v] = "executable" - else - spec.depends.fs[v] = "file" - end - end - end - - if type(subnode.uci_depends) == "table" then - for k, v in pairs(subnode.uci_depends) do - spec.depends = spec.depends or {} - spec.depends.uci = spec.depends.uci or {} - spec.depends.uci[k] = v - end - end - - if type(subnode.acl_depends) == "table" then - for _, acl in ipairs(subnode.acl_depends) do - spec.depends = spec.depends or {} - spec.depends.acl = spec.depends.acl or {} - spec.depends.acl[#spec.depends.acl + 1] = acl - end - end - - if (subnode.sysauth_authenticator ~= nil) or - (subnode.sysauth ~= nil and subnode.sysauth ~= false) - then - if subnode.sysauth_authenticator == "htmlauth" then - spec.auth = { - login = true, - methods = { "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then - spec.auth = { - login = false, - methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif subnode.module == "luci.controller.admin.uci" then - spec.auth = { - login = false, - methods = { "param:sid" } - } - end - elseif subnode.sysauth == false then - spec.auth = {} - end - - if not spec.action then - spec.title = nil - end - - spec.satisfied = check_depends(spec) - json.children = json.children or {} - json.children[subname] = tree_to_json(subnode, spec) - end - end - - return json -end - -function build_url(...) - local path = {...} - local url = { http.getenv("SCRIPT_NAME") or "" } - - local p - for _, p in ipairs(path) do - if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then - url[#url+1] = "/" - url[#url+1] = p - end - end - - if #path == 0 then - url[#url+1] = "/" - end - - return table.concat(url, "") -end - - -function error404(message) - http.status(404, "Not Found") - message = message or "Not Found" - - local function render() - local template = require "luci.template" - template.render("error404", {message=message}) - end - - if not util.copcall(render) then - http.prepare_content("text/plain") - http.write(message) - end - - return false -end - -function error500(message) - util.perror(message) - if not context.template_header_sent then - http.status(500, "Internal Server Error") - http.prepare_content("text/plain") - http.write(message) - else - require("luci.template") - if not util.copcall(luci.template.render, "error500", {message=message}) then - http.prepare_content("text/plain") - http.write(message) - end - end - return false -end - -local function determine_request_language() - local conf = require "luci.config" - assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'") - - local lang = conf.main.lang or "auto" - if lang == "auto" then - local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or "" - for aclang in aclang:gmatch("[%w_-]+") do - local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$") - if country and culture then - local cc = "%s_%s" %{ country, culture:lower() } - if conf.languages[cc] then - lang = cc - break - elseif conf.languages[country] then - lang = country - break - end - elseif conf.languages[aclang] then - lang = aclang - break - end - end - end - - if lang == "auto" then - lang = i18n.default - end - - i18n.setlanguage(lang) -end - -function httpdispatch(request, prefix) - http.context.request = request - - local r = {} - context.request = r - - local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) - - if prefix then - for _, node in ipairs(prefix) do - r[#r+1] = node - end - end - - local node - for node in pathinfo:gmatch("[^/%z]+") do - r[#r+1] = node - end - - determine_request_language() - - local stat, err = util.coxpcall(function() - dispatch(context.request) - end, error500) - - http.close() - - --context._disable_memtrace() -end - -local function require_post_security(target, args) - if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then - return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args) - end - - if type(target) == "table" then - if type(target.post) == "table" then - local param_name, required_val, request_val - - for param_name, required_val in pairs(target.post) do - request_val = http.formvalue(param_name) - - if (type(required_val) == "string" and - request_val ~= required_val) or - (required_val == true and request_val == nil) - then - return false - end - end - - return true - end - - return (target.post == true) - end - - return false -end - -function test_post_security() - if http.getenv("REQUEST_METHOD") ~= "POST" then - http.status(405, "Method Not Allowed") - http.header("Allow", "POST") - return false - end - - if http.formvalue("token") ~= context.authtoken then - http.status(403, "Forbidden") - luci.template.render("csrftoken") - return false - end - - return true -end - -local function session_retrieve(sid, allowed_users) - local sdat = util.ubus("session", "get", { ubus_rpc_session = sid }) - local sacl = util.ubus("session", "access", { ubus_rpc_session = sid }) - - if type(sdat) == "table" and - type(sdat.values) == "table" and - type(sdat.values.token) == "string" and - (not allowed_users or - util.contains(allowed_users, sdat.values.username)) - then - uci:set_session_id(sid) - return sid, sdat.values, type(sacl) == "table" and sacl or {} - end - - return nil, nil, nil -end - -local function session_setup(user, pass) - local login = util.ubus("session", "login", { - username = user, - password = pass, - timeout = tonumber(luci.config.sauth.sessiontime) - }) - - local rp = context.requestpath - and table.concat(context.requestpath, "/") or "" - - if type(login) == "table" and - type(login.ubus_rpc_session) == "string" - then - util.ubus("session", "set", { - ubus_rpc_session = login.ubus_rpc_session, - values = { token = sys.uniqueid(16) } - }) - nixio.syslog("info", tostring("luci: accepted login on /%s for %s from %s\n" - %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) - - return session_retrieve(login.ubus_rpc_session) - end - nixio.syslog("info", tostring("luci: failed login on /%s for %s from %s\n" - %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) -end - -local function check_authentication(method) - local auth_type, auth_param = method:match("^(%w+):(.+)$") - local sid, sdat - - if auth_type == "cookie" then - sid = http.getcookie(auth_param) - elseif auth_type == "param" then - sid = http.formvalue(auth_param) - elseif auth_type == "query" then - sid = http.formvalue(auth_param, true) - end - - return session_retrieve(sid) -end - -local function merge_trees(node_a, node_b) - for k, v in pairs(node_b) do - if k == "children" then - node_a.children = node_a.children or {} - - for name, spec in pairs(v) do - node_a.children[name] = merge_trees(node_a.children[name] or {}, spec) - end - else - node_a[k] = v - end - end - - if type(node_a.action) == "table" and - node_a.action.type == "firstchild" and - node_a.children == nil - then - node_a.satisfied = false - end - - return node_a -end - -local function apply_tree_acls(node, acl) - if type(node.children) == "table" then - for _, child in pairs(node.children) do - apply_tree_acls(child, acl) - end - end - - local perm - if type(node.depends) == "table" then - perm = check_acl_depends(node.depends.acl, acl["access-group"]) - else - perm = true - end - - if perm == nil then - node.satisfied = false - elseif perm == false then - node.readonly = true - end -end - -function menu_json(acl) - local tree = context.tree or createtree() - local lua_tree = tree_to_json(tree, { - action = { - ["type"] = "firstchild", - ["recurse"] = true - } - }) - - local json_tree = createtree_json() - local menu_tree = merge_trees(lua_tree, json_tree) - - if acl then - apply_tree_acls(menu_tree, acl) - end - - return menu_tree -end - -local function init_template_engine(ctx) - local tpl = require "luci.template" - local media = luci.config.main.mediaurlbase - - if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then - media = nil - for name, theme in pairs(luci.config.themes) do - if name:sub(1,1) ~= "." and pcall(tpl.Template, - "themes/%s/header" % fs.basename(theme)) then - media = theme - end - end - assert(media, "No valid theme found") - end - - local function _ifattr(cond, key, val, noescape) - if cond then - local env = getfenv(3) - local scope = (type(env.self) == "table") and env.self - if type(val) == "table" then - if not next(val) then - return '' - else - val = util.serialize_json(val) - end - end - - val = tostring(val or - (type(env[key]) ~= "function" and env[key]) or - (scope and type(scope[key]) ~= "function" and scope[key]) or "") - - if noescape ~= true then - val = xml.pcdata(val) - end - - return string.format(' %s="%s"', tostring(key), val) - else - return '' - end - end - - tpl.context.viewns = setmetatable({ - write = http.write; - include = function(name) tpl.Template(name):render(getfenv(2)) end; - translate = i18n.translate; - translatef = i18n.translatef; - export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; - striptags = xml.striptags; - pcdata = xml.pcdata; - media = media; - theme = fs.basename(media); - resource = luci.config.main.resourcebase; - ifattr = function(...) return _ifattr(...) end; - attr = function(...) return _ifattr(true, ...) end; - url = build_url; - }, {__index=function(tbl, key) - if key == "controller" then - return build_url() - elseif key == "REQUEST_URI" then - return build_url(unpack(ctx.requestpath)) - elseif key == "FULL_REQUEST_URI" then - local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") } - local query = http.getenv("QUERY_STRING") - if query and #query > 0 then - url[#url+1] = "?" - url[#url+1] = query - end - return table.concat(url, "") - elseif key == "token" then - return ctx.authtoken - else - return rawget(tbl, key) or _G[key] - end - end}) - - return tpl -end - -function is_authenticated(auth) - if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then - local sid, sdat, sacl - for _, method in ipairs(auth.methods) do - sid, sdat, sacl = check_authentication(method) - - if sid and sdat and sacl then - return sid, sdat, sacl - end - end - end -end - -local function ctx_append(ctx, name, node) - ctx.path = ctx.path or {} - ctx.path[#ctx.path + 1] = name - - ctx.acls = ctx.acls or {} - - local acls = (type(node.depends) == "table" and type(node.depends.acl) == "table") and node.depends.acl or {} - for _, acl in ipairs(acls) do - ctx.acls[_] = acl - end - - ctx.auth = node.auth or ctx.auth - ctx.cors = node.cors or ctx.cors - ctx.suid = node.setuser or ctx.suid - ctx.sgid = node.setgroup or ctx.sgid - - return ctx -end - -local function node_weight(node) - local weight = node.order or 9999 - - if weight > 9999 then - weight = 9999 - end - - if type(node.auth) == "table" and node.auth.login then - weight = weight + 10000 - end - - return weight -end - -local function resolve_firstchild(node, sacl, login_allowed, ctx) - local candidate = nil - local candidate_ctx = nil - - for name, child in pairs(node.children) do - if child.satisfied then - if not sacl then - local _ - _, _, sacl = is_authenticated(node.auth) - end - - local cacl = (type(child.depends) == "table") and child.depends.acl or nil - local login = login_allowed or (type(child.auth) == "table" and child.auth.login) - if login or check_acl_depends(cacl, sacl and sacl["access-group"]) ~= nil then - if child.title and type(child.action) == "table" then - local child_ctx = ctx_append(util.clone(ctx, true), name, child) - if child.action.type == "firstchild" then - if not candidate or node_weight(candidate) > node_weight(child) then - local have_grandchild = resolve_firstchild(child, sacl, login, child_ctx) - if have_grandchild then - candidate = child - candidate_ctx = child_ctx - end - end - elseif not child.firstchild_ineligible then - if not candidate or node_weight(candidate) > node_weight(child) then - candidate = child - candidate_ctx = child_ctx - end - end - end - end - end - end - - if candidate then - for k, v in pairs(candidate_ctx) do - ctx[k] = v - end - - return true - end - - return false -end - -local function resolve_page(tree, request_path) - local node = tree - local sacl = nil - local login = false - local ctx = {} - - for i, s in ipairs(request_path) do - node = node.children and node.children[s] - - if not node or not node.satisfied then - break - end - - ctx_append(ctx, s, node) - - if not sacl then - local _ - _, _, sacl = is_authenticated(node.auth) - end - - if not login and type(node.auth) == "table" and node.auth.login then - login = true - end - - if node.wildcard then - ctx.request_args = {} - ctx.request_path = util.clone(ctx.path, true) - - for j = i + 1, #request_path do - ctx.request_path[j] = request_path[j] - ctx.request_args[j - i] = request_path[j] - end - - break - end - end - - if node and type(node.action) == "table" and node.action.type == "firstchild" then - resolve_firstchild(node, sacl, login, ctx) - end - - ctx.acls = ctx.acls or {} - ctx.path = ctx.path or {} - ctx.request_args = ctx.request_args or {} - ctx.request_path = ctx.request_path or util.clone(request_path, true) - - node = tree - - for _, s in ipairs(ctx.path or {}) do - node = node.children[s] - assert(node, "Internal node resolve error") - end - - return node, ctx -end - -function dispatch(request) - --context._disable_memtrace = require "luci.debug".trap_memtrace("l") - local ctx = context - - local auth, cors, suid, sgid - local menu = menu_json() - local page, lookup_ctx = resolve_page(menu, request) - local action = (page and type(page.action) == "table") and page.action or {} - - local tpl = init_template_engine(ctx) - - ctx.args = lookup_ctx.request_args - ctx.path = lookup_ctx.path - ctx.dispatched = page - - ctx.requestpath = ctx.requestpath or lookup_ctx.request_path - ctx.requestargs = ctx.requestargs or lookup_ctx.request_args - ctx.requested = ctx.requested or page - - if type(lookup_ctx.auth) == "table" and next(lookup_ctx.auth) then - local sid, sdat, sacl = is_authenticated(lookup_ctx.auth) - - if not (sid and sdat and sacl) and lookup_ctx.auth.login then - local user = http.getenv("HTTP_AUTH_USER") - local pass = http.getenv("HTTP_AUTH_PASS") - - if user == nil and pass == nil then - user = http.formvalue("luci_username") - pass = http.formvalue("luci_password") - end - - if user and pass then - sid, sdat, sacl = session_setup(user, pass) - end - - if not sid then - context.path = {} - - http.status(403, "Forbidden") - http.header("X-LuCI-Login-Required", "yes") - - local scope = { duser = "root", fuser = user } - local ok, res = util.copcall(tpl.render_string, [[<% include("themes/" .. theme .. "/sysauth") %>]], scope) - if ok then - return res - end - return tpl.render("sysauth", scope) - end - - http.header("Set-Cookie", 'sysauth_%s=%s; path=%s; SameSite=Strict; HttpOnly%s' %{ - http.getenv("HTTPS") == "on" and "https" or "http", - sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or "" - }) - - http.redirect(build_url(unpack(ctx.requestpath))) - return - end - - if not sid or not sdat or not sacl then - http.status(403, "Forbidden") - http.header("X-LuCI-Login-Required", "yes") - return - end - - ctx.authsession = sid - ctx.authtoken = sdat.token - ctx.authuser = sdat.username - ctx.authacl = sacl - end - - if #lookup_ctx.acls > 0 then - local perm = check_acl_depends(lookup_ctx.acls, ctx.authacl and ctx.authacl["access-group"]) - if perm == nil then - http.status(403, "Forbidden") - return - end - - if page then - page.readonly = not perm - end - end - - if action.type == "arcombine" then - action = (#lookup_ctx.request_args > 0) and action.targets[2] or action.targets[1] - end - - if lookup_ctx.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then - luci.http.status(200, "OK") - luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*") - luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - return - end - - if require_post_security(action) then - if not test_post_security() then - return - end - end - - if lookup_ctx.sgid then - sys.process.setgroup(lookup_ctx.sgid) - end - - if lookup_ctx.suid then - sys.process.setuser(lookup_ctx.suid) - end - - if action.type == "view" then - tpl.render("view", { view = action.path }) - - elseif action.type == "call" then - local ok, mod = util.copcall(require, action.module) - if not ok then - error500(mod) - return - end - - local func = mod[action["function"]] - - assert(func ~= nil, - 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?') - - assert(type(func) == "function", - 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' .. - 'of type "' .. type(func) .. '".') - - local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {} - for _, s in ipairs(lookup_ctx.request_args) do - argv[#argv + 1] = s - end - - local ok, err = util.copcall(func, unpack(argv)) - if not ok then - error500(err) - end - - --elseif action.type == "firstchild" then - -- tpl.render("empty_node_placeholder", getfenv(1)) - - elseif action.type == "alias" then - local sub_request = {} - for name in action.path:gmatch("[^/]+") do - sub_request[#sub_request + 1] = name - end - - for _, s in ipairs(lookup_ctx.request_args) do - sub_request[#sub_request + 1] = s - end - - dispatch(sub_request) - - elseif action.type == "rewrite" then - local sub_request = { unpack(request) } - for i = 1, action.remove do - table.remove(sub_request, 1) - end - - local n = 1 - for s in action.path:gmatch("[^/]+") do - table.insert(sub_request, n, s) - n = n + 1 - end - - for _, s in ipairs(lookup_ctx.request_args) do - sub_request[#sub_request + 1] = s - end - - dispatch(sub_request) - - elseif action.type == "template" then - tpl.render(action.path, getfenv(1)) - - elseif action.type == "cbi" then - _cbi({ config = action.config, model = action.path }, unpack(lookup_ctx.request_args)) - - elseif action.type == "form" then - _form({ model = action.path }, unpack(lookup_ctx.request_args)) - - else - if not menu.children then - error404("No root node was registered, this usually happens if no module was installed.\n" .. - "Install luci-mod-admin-full and retry. " .. - "If the module is already installed, try removing the /tmp/luci-indexcache file.") - else - error404("No page is registered at '/" .. table.concat(lookup_ctx.request_path, "/") .. "'.\n" .. - "If this url belongs to an extension, make sure it is properly installed.\n" .. - "If the extension was recently installed, try removing the /tmp/luci-indexcache file.") - end - end -end - -local function hash_filelist(files) - local fprint = {} - local n = 0 - - for i, file in ipairs(files) do - local st = fs.stat(file) - if st then - fprint[n + 1] = '%x' % st.ino - fprint[n + 2] = '%x' % st.mtime - fprint[n + 3] = '%x' % st.size - n = n + 3 - end - end - - return nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".") -end - -local function read_cachefile(file, reader) - local euid = sys.process.info("uid") - local fuid = fs.stat(file, "uid") - local mode = fs.stat(file, "modestr") - - if euid ~= fuid or mode ~= "rw-------" then - return nil - end - - return reader(file) -end - -function createindex() - local controllers = { } - local base = "%s/controller/" % util.libpath() - local _, path - - for path in (fs.glob("%s*.lua" % base) or function() end) do - controllers[#controllers+1] = path - end - - for path in (fs.glob("%s*/*.lua" % base) or function() end) do - controllers[#controllers+1] = path - end - - local cachefile - - if indexcache then - cachefile = "%s.%s.lua" %{ indexcache, hash_filelist(controllers) } - - local res = read_cachefile(cachefile, function(path) return loadfile(path)() end) - if res then - index = res - return res - end - - for file in (fs.glob("%s.*.lua" % indexcache) or function() end) do - fs.unlink(file) - end - end - - index = {} - - for _, path in ipairs(controllers) do - local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".") - local mod = require(modname) - assert(mod ~= true, - "Invalid controller file found\n" .. - "The file '" .. path .. "' contains an invalid module line.\n" .. - "Please verify whether the module name is set to '" .. modname .. - "' - It must correspond to the file path!") - - local idx = mod.index - if type(idx) == "function" then - index[modname] = idx - end - end - - if cachefile then - local f = nixio.open(cachefile, "w", 600) - f:writeall(util.get_bytecode(index)) - f:close() - end -end - -function createtree_json() - local json = require "luci.jsonc" - local tree = {} - - local schema = { - action = "table", - auth = "table", - cors = "boolean", - depends = "table", - order = "number", - setgroup = "string", - setuser = "string", - title = "string", - wildcard = "boolean", - firstchild_ineligible = "boolean" - } - - local files = {} - local cachefile - - for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do - files[#files+1] = file - end - - if indexcache then - cachefile = "%s.%s.json" %{ indexcache, hash_filelist(files) } - - local res = read_cachefile(cachefile, function(path) return json.parse(fs.readfile(path) or "") end) - if res then - return res - end - - for file in (fs.glob("%s.*.json" % indexcache) or function() end) do - fs.unlink(file) - end - end - - for _, file in ipairs(files) do - local data = json.parse(fs.readfile(file) or "") - if type(data) == "table" then - for path, spec in pairs(data) do - if type(spec) == "table" then - local node = tree - - for s in path:gmatch("[^/]+") do - if s == "*" then - node.wildcard = true - break - end - - node.children = node.children or {} - node.children[s] = node.children[s] or {} - node = node.children[s] - end - - if node ~= tree then - for k, t in pairs(schema) do - if type(spec[k]) == t then - node[k] = spec[k] - end - end - - node.satisfied = check_depends(spec) - end - end - end - end - end - - if cachefile then - local f = nixio.open(cachefile, "w", 600) - f:writeall(json.stringify(tree)) - f:close() - end - - return tree -end - --- Build the index before if it does not exist yet. -function createtree() - if not index then - createindex() - end - - local ctx = context - local tree = {nodes={}, inreq=true} - - ctx.treecache = setmetatable({}, {__mode="v"}) - ctx.tree = tree - - local scope = setmetatable({}, {__index = luci.dispatcher}) - - for k, v in pairs(index) do - scope._NAME = k - setfenv(v, scope) - v() - end - - return tree -end - -function assign(path, clone, title, order) - local obj = node(unpack(path)) - obj.nodes = nil - obj.module = nil - - obj.title = title - obj.order = order - - setmetatable(obj, {__index = _create_node(clone)}) - - return obj -end - -function entry(path, target, title, order) - local c = node(unpack(path)) - - c.target = target - c.title = title - c.order = order - c.module = getfenv(2)._NAME - - return c -end - --- enabling the node. -function get(...) - return _create_node({...}) -end - -function node(...) - local c = _create_node({...}) - - c.module = getfenv(2)._NAME - c.auto = nil - - return c -end - -function lookup(...) - local i, path = nil, {} - for i = 1, select('#', ...) do - local name, arg = nil, tostring(select(i, ...)) - for name in arg:gmatch("[^/]+") do - path[#path+1] = name - end - end - - for i = #path, 1, -1 do - local node = context.treecache[table.concat(path, ".", 1, i)] - if node and (i == #path or node.leaf) then - return node, build_url(unpack(path)) - end - end -end - -function _create_node(path) - if #path == 0 then - return context.tree - end - - local name = table.concat(path, ".") - local c = context.treecache[name] - - if not c then - local last = table.remove(path) - local parent = _create_node(path) - - c = {nodes={}, auto=true, inreq=true} - - parent.nodes[last] = c - context.treecache[name] = c - end - - return c -end - --- Subdispatchers -- - -function firstchild() - return { type = "firstchild" } -end - -function firstnode() - return { type = "firstnode" } -end - -function alias(...) - return { type = "alias", req = { ... } } -end - -function rewrite(n, ...) - return { type = "rewrite", n = n, req = { ... } } -end - -function call(name, ...) - return { type = "call", argv = {...}, name = name } -end - -function post_on(params, name, ...) - return { - type = "call", - post = params, - argv = { ... }, - name = name - } -end - -function post(...) - return post_on(true, ...) -end - - -function template(name) - return { type = "template", view = name } -end - -function view(name) - return { type = "view", view = name } -end - - -function _cbi(self, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local http = require "luci.http" - local util = require "luci.util" - - local config = self.config or {} - local maps = cbi.load(self.model, ...) - - local state = nil - - local function has_uci_access(config, level) - local rv = util.ubus("session", "access", { - ubus_rpc_session = context.authsession, - scope = "uci", object = config, - ["function"] = level - }) - - return (type(rv) == "table" and rv.access == true) or false - end - - local i, res - for i, res in ipairs(maps) do - if util.instanceof(res, cbi.SimpleForm) then - io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n" - % self.model) - - io.stderr:write("please change %s to use the form() action instead.\n" - % table.concat(context.request, "/")) - end - - res.flow = config - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - local function _resolve_path(path) - return type(path) == "table" and build_url(unpack(path)) or path - end - - if config.on_valid_to and state and state > 0 and state < 2 then - http.redirect(_resolve_path(config.on_valid_to)) - return - end - - if config.on_changed_to and state and state > 1 then - http.redirect(_resolve_path(config.on_changed_to)) - return - end - - if config.on_success_to and state and state > 0 then - http.redirect(_resolve_path(config.on_success_to)) - return - end - - if config.state_handler then - if not config.state_handler(state, maps) then - return - end - end - - http.header("X-CBI-State", state or 0) - - if not config.noheader then - tpl.render("cbi/header", {state = state}) - end - - local redirect - local messages - local applymap = false - local pageaction = true - local parsechain = { } - local writable = false - - for i, res in ipairs(maps) do - if res.apply_needed and res.parsechain then - local c - for _, c in ipairs(res.parsechain) do - parsechain[#parsechain+1] = c - end - applymap = true - end - - if res.redirect then - redirect = redirect or res.redirect - end - - if res.pageaction == false then - pageaction = false - end - - if res.message then - messages = messages or { } - messages[#messages+1] = res.message - end - end - - for i, res in ipairs(maps) do - local is_readable_map = has_uci_access(res.config, "read") - local is_writable_map = has_uci_access(res.config, "write") - - writable = writable or is_writable_map - - res:render({ - firstmap = (i == 1), - redirect = redirect, - messages = messages, - pageaction = pageaction, - parsechain = parsechain, - readable = is_readable_map, - writable = is_writable_map - }) - end - - if not config.nofooter then - tpl.render("cbi/footer", { - flow = config, - pageaction = pageaction, - redirect = redirect, - state = state, - autoapply = config.autoapply, - trigger_apply = applymap, - writable = writable - }) - end -end - -function cbi(model, config) - return { - type = "cbi", - post = { ["cbi.submit"] = true }, - config = config, - model = model - } -end - - -function arcombine(trg1, trg2) - return { - type = "arcombine", - env = getfenv(), - targets = {trg1, trg2} - } -end - - -function _form(self, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local http = require "luci.http" - - local maps = luci.cbi.load(self.model, ...) - local state = nil - - local i, res - for i, res in ipairs(maps) do - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - http.header("X-CBI-State", state or 0) - tpl.render("header") - for i, res in ipairs(maps) do - res:render() - end - tpl.render("footer") -end - -function form(model) - return { - type = "form", - post = { ["cbi.submit"] = true }, - model = model - } -end - -translate = i18n.translate - --- This function does not actually translate the given argument but --- is used by build/i18n-scan.pl to find translatable entries. -function _(text) - return text -end diff --git a/modules/luci-base/luasrc/dispatcher.luadoc b/modules/luci-base/luasrc/dispatcher.luadoc deleted file mode 100644 index a77f8d8b07..0000000000 --- a/modules/luci-base/luasrc/dispatcher.luadoc +++ /dev/null @@ -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 _ -]] - diff --git a/modules/luci-base/luasrc/i18n.lua b/modules/luci-base/luasrc/i18n.lua deleted file mode 100644 index 323912b650..0000000000 --- a/modules/luci-base/luasrc/i18n.lua +++ /dev/null @@ -1,55 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -local tparser = require "luci.template.parser" -local util = require "luci.util" -local tostring = tostring - -module "luci.i18n" - -i18ndir = util.libpath() .. "/i18n/" -context = util.threadlocal() -default = "en" - - -function setlanguage(lang) - local code, subcode = lang:match("^([A-Za-z][A-Za-z])[%-_]([A-Za-z][A-Za-z])$") - if not (code and subcode) then - subcode = lang:match("^([A-Za-z][A-Za-z])$") - if not subcode then - return nil - end - end - - context.parent = code and code:lower() - context.lang = context.parent and context.parent.."-"..subcode:lower() or subcode:lower() - - if tparser.load_catalog(context.lang, i18ndir) and - tparser.change_catalog(context.lang) - then - return context.lang - - elseif context.parent then - if tparser.load_catalog(context.parent, i18ndir) and - tparser.change_catalog(context.parent) - then - return context.parent - end - end - - return nil -end - -function translate(key) - return tparser.translate(key) or key -end - -function translatef(key, ...) - return tostring(translate(key)):format(...) -end - -function dump() - local rv = {} - tparser.get_translations(function(k, v) rv[k] = v end) - return rv -end diff --git a/modules/luci-base/luasrc/i18n.luadoc b/modules/luci-base/luasrc/i18n.luadoc deleted file mode 100644 index b76c298565..0000000000 --- a/modules/luci-base/luasrc/i18n.luadoc +++ /dev/null @@ -1,42 +0,0 @@ ----[[ -LuCI translation library. -]] -module "luci.i18n" - ----[[ -Set the context default translation language. - -@class function -@name setlanguage -@param lang An IETF/BCP 47 language tag or ISO3166 country code, e.g. "en-US" or "de" -@return The effective loaded language, e.g. "en" for "en-US" - or nil on failure -]] - ----[[ -Return the translated value for a specific translation key. - -@class function -@name translate -@param key Default translation text -@return Translated string -]] - ----[[ -Return the translated value for a specific translation key and use it as sprintf pattern. - -@class function -@name translatef -@param key Default translation text -@param ... Format parameters -@return Translated and formatted string -]] - ----[[ -Return all currently loaded translation strings as a key-value table. The key is the -hexadecimal representation of the translation key while the value is the translated -text content. - -@class function -@name dump -@return Key-value translation string table. -]] diff --git a/modules/luci-base/luasrc/model/uci.lua b/modules/luci-base/luasrc/model/uci.lua deleted file mode 100644 index 816f6f2053..0000000000 --- a/modules/luci-base/luasrc/model/uci.lua +++ /dev/null @@ -1,508 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -local os = require "os" -local util = require "luci.util" -local table = require "table" - - -local setmetatable, rawget, rawset = setmetatable, rawget, rawset -local require, getmetatable, assert = require, getmetatable, assert -local error, pairs, ipairs, select = error, pairs, ipairs, select -local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack - --- The typical workflow for UCI is: Get a cursor instance from the --- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), --- save the changes to the staging area via Cursor.save and finally --- Cursor.commit the data to the actual config files. --- LuCI then needs to Cursor.apply the changes so daemons etc. are --- reloaded. -module "luci.model.uci" - -local ERRSTR = { - "Invalid command", - "Invalid argument", - "Method not found", - "Entry not found", - "No data", - "Permission denied", - "Timeout", - "Not supported", - "Unknown error", - "Connection failed" -} - -local session_id = nil - -local function call(cmd, args) - if type(args) == "table" and session_id then - args.ubus_rpc_session = session_id - end - return util.ubus("uci", cmd, args) -end - - -function cursor() - return _M -end - -function cursor_state() - return _M -end - -function substate(self) - return self -end - - -function get_confdir(self) - return "/etc/config" -end - -function get_savedir(self) - return "/tmp/.uci" -end - -function get_session_id(self) - return session_id -end - -function set_confdir(self, directory) - return false -end - -function set_savedir(self, directory) - return false -end - -function set_session_id(self, id) - session_id = id - return true -end - - -function load(self, config) - return true -end - -function save(self, config) - return true -end - -function unload(self, config) - return true -end - - -function changes(self, config) - local rv, err = call("changes", { config = config }) - - if type(rv) == "table" and type(rv.changes) == "table" then - return rv.changes - elseif err then - return nil, ERRSTR[err] - else - return { } - end -end - - -function revert(self, config) - local _, err = call("revert", { config = config }) - return (err == nil), ERRSTR[err] -end - -function commit(self, config) - local _, err = call("commit", { config = config }) - return (err == nil), ERRSTR[err] -end - -function apply(self, rollback) - local _, err - - if rollback then - local sys = require "luci.sys" - local conf = require "luci.config" - local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 90) or 0 - - _, err = call("apply", { - timeout = (timeout > 90) and timeout or 90, - rollback = true - }) - - if not err then - local now = os.time() - local token = sys.uniqueid(16) - - util.ubus("session", "set", { - ubus_rpc_session = "00000000000000000000000000000000", - values = { - rollback = { - token = token, - session = session_id, - timeout = now + timeout - } - } - }) - - return token - end - else - _, err = call("changes", {}) - - if not err then - if type(_) == "table" and type(_.changes) == "table" then - local k, v - for k, v in pairs(_.changes) do - _, err = call("commit", { config = k }) - if err then - break - end - end - end - end - - if not err then - _, err = call("apply", { rollback = false }) - end - end - - return (err == nil), ERRSTR[err] -end - -function confirm(self, token) - local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending() - - if is_pending then - if token ~= rollback_token then - return false, "Permission denied" - end - - local _, err = util.ubus("uci", "confirm", { - ubus_rpc_session = rollback_sid - }) - - if not err then - util.ubus("session", "set", { - ubus_rpc_session = "00000000000000000000000000000000", - values = { rollback = {} } - }) - end - - return (err == nil), ERRSTR[err] - end - - return false, "No data" -end - -function rollback(self) - local is_pending, time_remaining, rollback_sid = self:rollback_pending() - - if is_pending then - local _, err = util.ubus("uci", "rollback", { - ubus_rpc_session = rollback_sid - }) - - if not err then - util.ubus("session", "set", { - ubus_rpc_session = "00000000000000000000000000000000", - values = { rollback = {} } - }) - end - - return (err == nil), ERRSTR[err] - end - - return false, "No data" -end - -function rollback_pending(self) - local rv, err = util.ubus("session", "get", { - ubus_rpc_session = "00000000000000000000000000000000", - keys = { "rollback" } - }) - - local now = os.time() - - if type(rv) == "table" and - type(rv.values) == "table" and - type(rv.values.rollback) == "table" and - type(rv.values.rollback.token) == "string" and - type(rv.values.rollback.session) == "string" and - type(rv.values.rollback.timeout) == "number" and - rv.values.rollback.timeout > now - then - return true, - rv.values.rollback.timeout - now, - rv.values.rollback.session, - rv.values.rollback.token - end - - return false, ERRSTR[err] -end - - -function foreach(self, config, stype, callback) - if type(callback) == "function" then - local rv, err = call("get", { - config = config, - type = stype - }) - - if type(rv) == "table" and type(rv.values) == "table" then - local sections = { } - local res = false - local index = 1 - - local _, section - for _, section in pairs(rv.values) do - section[".index"] = section[".index"] or index - sections[index] = section - index = index + 1 - end - - table.sort(sections, function(a, b) - return a[".index"] < b[".index"] - end) - - for _, section in ipairs(sections) do - local continue = callback(section) - res = true - if continue == false then - break - end - end - return res - else - return false, ERRSTR[err] or "No data" - end - else - return false, "Invalid argument" - end -end - -local function _get(self, operation, config, section, option) - if section == nil then - return nil - elseif type(option) == "string" and option:byte(1) ~= 46 then - local rv, err = call(operation, { - config = config, - section = section, - option = option - }) - - if type(rv) == "table" then - return rv.value or nil - elseif err then - return false, ERRSTR[err] - else - return nil - end - elseif option == nil then - local values = self:get_all(config, section) - if values then - return values[".type"], values[".name"] - else - return nil - end - else - return false, "Invalid argument" - end -end - -function get(self, ...) - return _get(self, "get", ...) -end - -function get_state(self, ...) - return _get(self, "state", ...) -end - -function get_all(self, config, section) - local rv, err = call("get", { - config = config, - section = section - }) - - if type(rv) == "table" and type(rv.values) == "table" then - return rv.values - elseif err then - return false, ERRSTR[err] - else - return nil - end -end - -function get_bool(self, ...) - local val = self:get(...) - return (val == "1" or val == "true" or val == "yes" or val == "on") -end - -function get_first(self, config, stype, option, default) - local rv = default - - self:foreach(config, stype, function(s) - local val = not option and s[".name"] or s[option] - - if type(default) == "number" then - val = tonumber(val) - elseif type(default) == "boolean" then - val = (val == "1" or val == "true" or - val == "yes" or val == "on") - end - - if val ~= nil then - rv = val - return false - end - end) - - return rv -end - -function get_list(self, config, section, option) - if config and section and option then - local val = self:get(config, section, option) - return (type(val) == "table" and val or { val }) - end - return { } -end - - -function section(self, config, stype, name, values) - local rv, err = call("add", { - config = config, - type = stype, - name = name, - values = values - }) - - if type(rv) == "table" then - return rv.section - elseif err then - return false, ERRSTR[err] - else - return nil - end -end - - -function add(self, config, stype) - return self:section(config, stype) -end - -function set(self, config, section, option, ...) - if select('#', ...) == 0 then - local sname, err = self:section(config, option, section) - return (not not sname), err - else - local _, err = call("set", { - config = config, - section = section, - values = { [option] = select(1, ...) } - }) - return (err == nil), ERRSTR[err] - end -end - -function set_list(self, config, section, option, value) - if section == nil or option == nil then - return false - elseif value == nil or (type(value) == "table" and #value == 0) then - return self:delete(config, section, option) - elseif type(value) == "table" then - return self:set(config, section, option, value) - else - return self:set(config, section, option, { value }) - end -end - -function tset(self, config, section, values) - local _, err = call("set", { - config = config, - section = section, - values = values - }) - return (err == nil), ERRSTR[err] -end - -function reorder(self, config, section, index) - local sections - - if type(section) == "string" and type(index) == "number" then - local pos = 0 - - sections = { } - - self:foreach(config, nil, function(s) - if pos == index then - pos = pos + 1 - end - - if s[".name"] ~= section then - pos = pos + 1 - sections[pos] = s[".name"] - else - sections[index + 1] = section - end - end) - elseif type(section) == "table" then - sections = section - else - return false, "Invalid argument" - end - - local _, err = call("order", { - config = config, - sections = sections - }) - - return (err == nil), ERRSTR[err] -end - - -function delete(self, config, section, option) - local _, err = call("delete", { - config = config, - section = section, - option = option - }) - return (err == nil), ERRSTR[err] -end - -function delete_all(self, config, stype, comparator) - local _, err - if type(comparator) == "table" then - _, err = call("delete", { - config = config, - type = stype, - match = comparator - }) - elseif type(comparator) == "function" then - local rv = call("get", { - config = config, - type = stype - }) - - if type(rv) == "table" and type(rv.values) == "table" then - local sname, section - for sname, section in pairs(rv.values) do - if comparator(section) then - _, err = call("delete", { - config = config, - section = sname - }) - end - end - end - elseif comparator == nil then - _, err = call("delete", { - config = config, - type = stype - }) - else - return false, "Invalid argument" - end - - return (err == nil), ERRSTR[err] -end diff --git a/modules/luci-base/luasrc/model/uci.luadoc b/modules/luci-base/luasrc/model/uci.luadoc deleted file mode 100644 index 0189d49aa1..0000000000 --- a/modules/luci-base/luasrc/model/uci.luadoc +++ /dev/null @@ -1,369 +0,0 @@ ----[[ -LuCI UCI model library. - -The typical workflow for UCI is: Get a cursor instance from the -cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), -save the changes to the staging area via Cursor.save and finally -Cursor.commit the data to the actual config files. -LuCI then needs to Cursor.apply the changes so daemons etc. are -reloaded. -@cstyle instance -]] -module "luci.model.uci" - ----[[ -Create a new UCI-Cursor. - -@class function -@name cursor -@return UCI-Cursor -]] - ----[[ -Create a new Cursor initialized to the state directory. - -@class function -@name cursor_state -@return UCI cursor -]] - ----[[ -Applies UCI configuration changes. - -If the rollback parameter is set to true, the apply function will invoke the -rollback mechanism which causes the configuration to be automatically reverted -if no confirm() call occurs within a certain timeout. - -The current default timeout is 30s and can be increased using the -"luci.apply.timeout" uci configuration key. - -@class function -@name Cursor.apply -@param rollback Enable rollback mechanism -@return Boolean whether operation succeeded -]] - ----[[ -Confirms UCI apply process. - -If a previous UCI apply with rollback has been invoked using apply(true), -this function confirms the process and cancels the pending rollback timer. - -If no apply with rollback session is active, the function has no effect and -returns with a "No data" error. - -@class function -@name Cursor.confirm -@return Boolean whether operation succeeded -]] - ----[[ -Cancels UCI apply process. - -If a previous UCI apply with rollback has been invoked using apply(true), -this function cancels the process and rolls back the configuration to the -pre-apply state. - -If no apply with rollback session is active, the function has no effect and -returns with a "No data" error. - -@class function -@name Cursor.rollback -@return Boolean whether operation succeeded -]] - ----[[ -Checks whether a pending rollback is scheduled. - -If a previous UCI apply with rollback has been invoked using apply(true), -and has not been confirmed or rolled back yet, this function returns true -and the remaining time until rollback in seconds. If no rollback is pending, -the function returns false. On error, the function returns false and an -additional string describing the error. - -@class function -@name Cursor.rollback_pending -@return Boolean whether rollback is pending -@return Remaining time in seconds -]] - ----[[ -Delete all sections of a given type that match certain criteria. - -@class function -@name Cursor.delete_all -@param config UCI config -@param type UCI section type -@param comparator Function that will be called for each section and returns - a boolean whether to delete the current section (optional) -]] - ----[[ -Create a new section and initialize it with data. - -@class function -@name Cursor.section -@param config UCI config -@param type UCI section type -@param name UCI section name (optional) -@param values Table of key - value pairs to initialize the section with -@return Name of created section -]] - ----[[ -Updated the data of a section using data from a table. - -@class function -@name Cursor.tset -@param config UCI config -@param section UCI section name (optional) -@param values Table of key - value pairs to update the section with -]] - ----[[ -Get a boolean option and return it's value as true or false. - -@class function -@name Cursor.get_bool -@param config UCI config -@param section UCI section name -@param option UCI option -@return Boolean -]] - ----[[ -Get an option or list and return values as table. - -@class function -@name Cursor.get_list -@param config UCI config -@param section UCI section name -@param option UCI option -@return table. If the option was not found, you will simply get an empty - table. -]] - ----[[ -Get the given option from the first section with the given type. - -@class function -@name Cursor.get_first -@param config UCI config -@param type UCI section type -@param option UCI option (optional) -@param default Default value (optional) -@return UCI value -]] - ----[[ -Set given values as list. Setting a list option to an empty list -has the same effect as deleting the option. - -@class function -@name Cursor.set_list -@param config UCI config -@param section UCI section name -@param option UCI option -@param value Value or table. Non-table values will be set as single - item UCI list. -@return Boolean whether operation succeeded -]] - ----[[ -Create a sub-state of this cursor. - -The sub-state is tied to the parent cursor, means it the parent unloads or -loads configs, the sub state will do so as well. - -@class function -@name Cursor.substate -@return UCI state cursor tied to the parent cursor -]] - ----[[ -Add an anonymous section. - -@class function -@name Cursor.add -@param config UCI config -@param type UCI section type -@return Name of created section -]] - ----[[ -Get a table of saved but uncommitted changes. - -@class function -@name Cursor.changes -@param config UCI config -@return Table of changes -@see Cursor.save -]] - ----[[ -Commit saved changes. - -@class function -@name Cursor.commit -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.revert -@see Cursor.save -]] - ----[[ -Deletes a section or an option. - -@class function -@name Cursor.delete -@param config UCI config -@param section UCI section name -@param option UCI option (optional) -@return Boolean whether operation succeeded -]] - ----[[ -Call a function for every section of a certain type. - -@class function -@name Cursor.foreach -@param config UCI config -@param type UCI section type -@param callback Function to be called -@return Boolean whether operation succeeded -]] - ----[[ -Get a section type or an option - -@class function -@name Cursor.get -@param config UCI config -@param section UCI section name -@param option UCI option (optional) -@return UCI value -]] - ----[[ -Get all sections of a config or all values of a section. - -@class function -@name Cursor.get_all -@param config UCI config -@param section UCI section name (optional) -@return Table of UCI sections or table of UCI values -]] - ----[[ -Manually load a config. - -@class function -@name Cursor.load -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.save -@see Cursor.unload -]] - ----[[ -Revert saved but uncommitted changes. - -@class function -@name Cursor.revert -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.commit -@see Cursor.save -]] - ----[[ -Saves changes made to a config to make them committable. - -@class function -@name Cursor.save -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.load -@see Cursor.unload -]] - ----[[ -Set a value or create a named section. - -When invoked with three arguments `config`, `sectionname`, `sectiontype`, -then a named section of the given type is created. - -When invoked with four arguments `config`, `sectionname`, `optionname` and -`optionvalue` then the value of the specified option is set to the given value. - -@class function -@name Cursor.set -@param config UCI config -@param section UCI section name -@param option UCI option or UCI section type -@param value UCI value or nothing if you want to create a section -@return Boolean whether operation succeeded -]] - ----[[ -Get the configuration directory. - -@class function -@name Cursor.get_confdir -@return Configuration directory -]] - ----[[ -Get the directory for uncomitted changes. - -@class function -@name Cursor.get_savedir -@return Save directory -]] - ----[[ -Get the effective session ID. - -@class function -@name Cursor.get_session_id -@return String containing the session ID -]] - ----[[ -Set the configuration directory. - -@class function -@name Cursor.set_confdir -@param directory UCI configuration directory -@return Boolean whether operation succeeded -]] - ----[[ -Set the directory for uncommitted changes. - -@class function -@name Cursor.set_savedir -@param directory UCI changes directory -@return Boolean whether operation succeeded -]] - ----[[ -Set the effective session ID. - -@class function -@name Cursor.set_session_id -@param id String containing the session ID to set -@return Boolean whether operation succeeded -]] - ----[[ -Discard changes made to a config. - -@class function -@name Cursor.unload -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.load -@see Cursor.save -]] - diff --git a/modules/luci-base/luasrc/sgi/cgi.lua b/modules/luci-base/luasrc/sgi/cgi.lua deleted file mode 100644 index 400db4710d..0000000000 --- a/modules/luci-base/luasrc/sgi/cgi.lua +++ /dev/null @@ -1,73 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -exectime = os.clock() -module("luci.sgi.cgi", package.seeall) -local ltn12 = require("luci.ltn12") -require("nixio.util") -require("luci.http") -require("luci.sys") -require("luci.dispatcher") - --- Limited source to avoid endless blocking -local function limitsource(handle, limit) - limit = limit or 0 - local BLOCKSIZE = ltn12.BLOCKSIZE - - return function() - if limit < 1 then - handle:close() - return nil - else - local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit - limit = limit - read - - local chunk = handle:read(read) - if not chunk then handle:close() end - return chunk - end - end -end - -function run() - local r = luci.http.Request( - luci.sys.getenv(), - limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))), - ltn12.sink.file(io.stderr) - ) - - local x = coroutine.create(luci.dispatcher.httpdispatch) - local hcache = "" - local active = true - - while coroutine.status(x) ~= "dead" do - local res, id, data1, data2 = coroutine.resume(x, r) - - if not res then - print("Status: 500 Internal Server Error") - print("Content-Type: text/plain\n") - print(id) - break; - end - - if active then - if id == 1 then - io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n") - elseif id == 2 then - hcache = hcache .. data1 .. ": " .. data2 .. "\r\n" - elseif id == 3 then - io.write(hcache) - io.write("\r\n") - elseif id == 4 then - io.write(tostring(data1 or "")) - elseif id == 5 then - io.flush() - io.close() - active = false - elseif id == 6 then - data1:copyz(nixio.stdout, data2) - data1:close() - end - end - end -end diff --git a/modules/luci-base/luasrc/sgi/uhttpd.lua b/modules/luci-base/luasrc/sgi/uhttpd.lua deleted file mode 100644 index 4cd3649c62..0000000000 --- a/modules/luci-base/luasrc/sgi/uhttpd.lua +++ /dev/null @@ -1,99 +0,0 @@ --- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -require "nixio.util" -require "luci.http" -require "luci.sys" -require "luci.dispatcher" -require "luci.ltn12" - -function handle_request(env) - exectime = os.clock() - local renv = { - CONTENT_LENGTH = env.CONTENT_LENGTH, - CONTENT_TYPE = env.CONTENT_TYPE, - REQUEST_METHOD = env.REQUEST_METHOD, - REQUEST_URI = env.REQUEST_URI, - PATH_INFO = env.PATH_INFO, - SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""), - SCRIPT_FILENAME = env.SCRIPT_NAME, - SERVER_PROTOCOL = env.SERVER_PROTOCOL, - QUERY_STRING = env.QUERY_STRING, - DOCUMENT_ROOT = env.DOCUMENT_ROOT, - HTTPS = env.HTTPS, - REDIRECT_STATUS = env.REDIRECT_STATUS, - REMOTE_ADDR = env.REMOTE_ADDR, - REMOTE_NAME = env.REMOTE_NAME, - REMOTE_PORT = env.REMOTE_PORT, - REMOTE_USER = env.REMOTE_USER, - SERVER_ADDR = env.SERVER_ADDR, - SERVER_NAME = env.SERVER_NAME, - SERVER_PORT = env.SERVER_PORT - } - - local k, v - for k, v in pairs(env.headers) do - k = k:upper():gsub("%-", "_") - renv["HTTP_" .. k] = v - end - - local len = tonumber(env.CONTENT_LENGTH) or 0 - local function recv() - if len > 0 then - local rlen, rbuf = uhttpd.recv(4096) - if rlen >= 0 then - len = len - rlen - return rbuf - end - end - return nil - end - - local send = uhttpd.send - - local req = luci.http.Request( - renv, recv, luci.ltn12.sink.file(io.stderr) - ) - - - local x = coroutine.create(luci.dispatcher.httpdispatch) - local hcache = { } - local active = true - - while coroutine.status(x) ~= "dead" do - local res, id, data1, data2 = coroutine.resume(x, req) - - if not res then - send("Status: 500 Internal Server Error\r\n") - send("Content-Type: text/plain\r\n\r\n") - send(tostring(id)) - break - end - - if active then - if id == 1 then - send("Status: ") - send(tostring(data1)) - send(" ") - send(tostring(data2)) - send("\r\n") - elseif id == 2 then - hcache[data1] = data2 - elseif id == 3 then - for k, v in pairs(hcache) do - send(tostring(k)) - send(": ") - send(tostring(v)) - send("\r\n") - end - send("\r\n") - elseif id == 4 then - send(tostring(data1 or "")) - elseif id == 5 then - active = false - elseif id == 6 then - data1:copyz(nixio.stdout, data2) - end - end - end -end diff --git a/modules/luci-base/luasrc/store.lua b/modules/luci-base/luasrc/store.lua deleted file mode 100644 index a735981137..0000000000 --- a/modules/luci-base/luasrc/store.lua +++ /dev/null @@ -1,6 +0,0 @@ --- Copyright 2009 Steven Barth <steven@midlink.org> --- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -module("luci.store", util.threadlocal) diff --git a/modules/luci-base/luasrc/sys.lua b/modules/luci-base/luasrc/sys.lua deleted file mode 100644 index e6eb762e48..0000000000 --- a/modules/luci-base/luasrc/sys.lua +++ /dev/null @@ -1,615 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -local io = require "io" -local os = require "os" -local table = require "table" -local nixio = require "nixio" -local fs = require "nixio.fs" -local uci = require "luci.model.uci" - -local luci = {} -luci.util = require "luci.util" -luci.ip = require "luci.ip" - -local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack = - tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack - - -module "luci.sys" - -function call(...) - return os.execute(...) / 256 -end - -exec = luci.util.exec - --- containing the whole environment is returned otherwise this function returns --- the corresponding string value for the given name or nil if no such variable --- exists. -getenv = nixio.getenv - -function hostname(newname) - if type(newname) == "string" and #newname > 0 then - fs.writefile( "/proc/sys/kernel/hostname", newname ) - return newname - else - return nixio.uname().nodename - end -end - -function httpget(url, stream, target) - if not target then - local source = stream and io.popen or luci.util.exec - return source("wget -qO- %s" % luci.util.shellquote(url)) - else - return os.execute("wget -qO %s %s" % - {luci.util.shellquote(target), luci.util.shellquote(url)}) - end -end - -function reboot() - return os.execute("reboot >/dev/null 2>&1") -end - -function syslog() - return luci.util.exec("logread") -end - -function dmesg() - return luci.util.exec("dmesg") -end - -function uniqueid(bytes) - local rand = fs.readfile("/dev/urandom", bytes) - return rand and nixio.bin.hexlify(rand) -end - -function uptime() - return nixio.sysinfo().uptime -end - - -net = {} - -local function _nethints(what, callback) - local _, k, e, mac, ip, name, duid, iaid - local cur = uci.cursor() - local ifn = { } - local hosts = { } - local lookup = { } - - local function _add(i, ...) - local k = select(i, ...) - if k then - if not hosts[k] then hosts[k] = { } end - hosts[k][1] = select(1, ...) or hosts[k][1] - hosts[k][2] = select(2, ...) or hosts[k][2] - hosts[k][3] = select(3, ...) or hosts[k][3] - hosts[k][4] = select(4, ...) or hosts[k][4] - end - end - - luci.ip.neighbors(nil, function(neigh) - if neigh.mac and neigh.family == 4 then - _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil) - elseif neigh.mac and neigh.family == 6 then - _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil) - end - end) - - if fs.access("/etc/ethers") then - for e in io.lines("/etc/ethers") do - mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)") - mac = luci.ip.checkmac(mac) - if mac and name then - if luci.ip.checkip4(name) then - _add(what, mac, name, nil, nil) - else - _add(what, mac, nil, nil, name) - end - end - end - end - - cur:foreach("dhcp", "dnsmasq", - function(s) - if s.leasefile and fs.access(s.leasefile) then - for e in io.lines(s.leasefile) do - mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)") - mac = luci.ip.checkmac(mac) - if mac and ip then - _add(what, mac, ip, nil, name ~= "*" and name) - end - end - end - end - ) - - cur:foreach("dhcp", "odhcpd", - function(s) - if type(s.leasefile) == "string" and fs.access(s.leasefile) then - for e in io.lines(s.leasefile) do - duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+") - mac = net.duid_to_mac(duid) - if mac then - if ip and iaid == "ipv4" then - _add(what, mac, ip, nil, name ~= "*" and name) - elseif ip then - _add(what, mac, nil, ip, name ~= "*" and name) - end - end - end - end - end - ) - - cur:foreach("dhcp", "host", - function(s) - for mac in luci.util.imatch(s.mac) do - mac = luci.ip.checkmac(mac) - if mac then - _add(what, mac, s.ip, nil, s.name) - end - end - end) - - for _, e in ipairs(nixio.getifaddrs()) do - if e.name ~= "lo" then - ifn[e.name] = ifn[e.name] or { } - if e.family == "packet" and e.addr and #e.addr == 17 then - ifn[e.name][1] = e.addr:upper() - elseif e.family == "inet" then - ifn[e.name][2] = e.addr - elseif e.family == "inet6" then - ifn[e.name][3] = e.addr - end - end - end - - for _, e in pairs(ifn) do - if e[what] and (e[2] or e[3]) then - _add(what, e[1], e[2], e[3], e[4]) - end - end - - for _, e in pairs(hosts) do - lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3]) - end - - if #lookup > 0 then - lookup = luci.util.ubus("network.rrdns", "lookup", { - addrs = lookup, - timeout = 250, - limit = 1000 - }) or { } - end - - for _, e in luci.util.kspairs(hosts) do - callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4]) - end -end - --- Each entry contains the values in the following order: --- [ "mac", "name" ] -function net.mac_hints(callback) - if callback then - _nethints(1, function(mac, v4, v6, name) - name = name or v4 - if name and name ~= mac then - callback(mac, name or v4) - end - end) - else - local rv = { } - _nethints(1, function(mac, v4, v6, name) - name = name or v4 - if name and name ~= mac then - rv[#rv+1] = { mac, name or v4 } - end - end) - return rv - end -end - --- Each entry contains the values in the following order: --- [ "ip", "name" ] -function net.ipv4_hints(callback) - if callback then - _nethints(2, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v4 then - callback(v4, name) - end - end) - else - local rv = { } - _nethints(2, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v4 then - rv[#rv+1] = { v4, name } - end - end) - return rv - end -end - --- Each entry contains the values in the following order: --- [ "ip", "name" ] -function net.ipv6_hints(callback) - if callback then - _nethints(3, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v6 then - callback(v6, name) - end - end) - else - local rv = { } - _nethints(3, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v6 then - rv[#rv+1] = { v6, name } - end - end) - return rv - end -end - -function net.host_hints(callback) - if callback then - _nethints(1, function(mac, v4, v6, name) - if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then - callback(mac, v4, v6, name) - end - end) - else - local rv = { } - _nethints(1, function(mac, v4, v6, name) - if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then - local e = { } - if v4 then e.ipv4 = v4 end - if v6 then e.ipv6 = v6 end - if name then e.name = name end - rv[mac] = e - end - end) - return rv - end -end - -function net.conntrack(callback) - local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack") - if not ok or not nfct then - return nil - end - - local line, connt = nil, (not callback) and { } - for line in nfct do - local fam, l3, l4, rest = - line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$") - - local timeout, tuples = rest:match("^(%d+) +(.+)$") - - if not tuples then - tuples = rest - end - - if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then - l4 = nixio.getprotobynumber(l4) - - local entry = { - bytes = 0, - packets = 0, - layer3 = fam, - layer4 = l4 and l4.name or "unknown", - timeout = tonumber(timeout, 10) - } - - local key, val - for key, val in tuples:gmatch("(%w+)=(%S+)") do - if key == "bytes" or key == "packets" then - entry[key] = entry[key] + tonumber(val, 10) - elseif key == "src" or key == "dst" then - if entry[key] == nil then - entry[key] = luci.ip.new(val):string() - end - elseif key == "sport" or key == "dport" then - if entry[key] == nil then - entry[key] = val - end - elseif val then - entry[key] = val - end - end - - if callback then - callback(entry) - else - connt[#connt+1] = entry - end - end - end - - return callback and true or connt -end - -function net.devices() - local devs = {} - local seen = {} - for k, v in ipairs(nixio.getifaddrs()) do - if v.name and not seen[v.name] then - seen[v.name] = true - devs[#devs+1] = v.name - end - end - return devs -end - -function net.duid_to_mac(duid) - local b1, b2, b3, b4, b5, b6 - - if type(duid) == "string" then - -- DUID-LLT / Ethernet - if #duid == 28 then - b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$") - - -- DUID-LL / Ethernet - elseif #duid == 20 then - b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") - - -- DUID-LL / Ethernet (Without Header) - elseif #duid == 12 then - b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") - end - end - - return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":")) -end - -process = {} - -function process.info(key) - local s = {uid = nixio.getuid(), gid = nixio.getgid()} - return not key and s or s[key] -end - -function process.list() - local data = {} - local k - local ps = luci.util.execi("/bin/busybox top -bn1") - - if not ps then - return - end - - for line in ps do - local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match( - "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][<NW ][<N ]) +(%d+m?) +(%d+%%) +(%d+%%) +(.+)" - ) - - local idx = tonumber(pid) - if idx and not cmd:match("top %-bn1") then - data[idx] = { - ['PID'] = pid, - ['PPID'] = ppid, - ['USER'] = user, - ['STAT'] = stat, - ['VSZ'] = vsz, - ['%MEM'] = mem, - ['%CPU'] = cpu, - ['COMMAND'] = cmd - } - end - end - - return data -end - -function process.setgroup(gid) - return nixio.setgid(gid) -end - -function process.setuser(uid) - return nixio.setuid(uid) -end - -process.signal = nixio.kill - -local function xclose(fd) - if fd and fd:fileno() > 2 then - fd:close() - end -end - -function process.exec(command, stdout, stderr, nowait) - local out_r, out_w, err_r, err_w - if stdout then out_r, out_w = nixio.pipe() end - if stderr then err_r, err_w = nixio.pipe() end - - local pid = nixio.fork() - if pid == 0 then - nixio.chdir("/") - - local null = nixio.open("/dev/null", "w+") - if null then - nixio.dup(out_w or null, nixio.stdout) - nixio.dup(err_w or null, nixio.stderr) - nixio.dup(null, nixio.stdin) - xclose(out_w) - xclose(out_r) - xclose(err_w) - xclose(err_r) - xclose(null) - end - - nixio.exec(unpack(command)) - os.exit(-1) - end - - local _, pfds, rv = nil, {}, { code = -1, pid = pid } - - xclose(out_w) - xclose(err_w) - - if out_r then - pfds[#pfds+1] = { - fd = out_r, - cb = type(stdout) == "function" and stdout, - name = "stdout", - events = nixio.poll_flags("in", "err", "hup") - } - end - - if err_r then - pfds[#pfds+1] = { - fd = err_r, - cb = type(stderr) == "function" and stderr, - name = "stderr", - events = nixio.poll_flags("in", "err", "hup") - } - end - - while #pfds > 0 do - local nfds, err = nixio.poll(pfds, -1) - if not nfds and err ~= nixio.const.EINTR then - break - end - - local i - for i = #pfds, 1, -1 do - local rfd = pfds[i] - if rfd.revents > 0 then - local chunk, err = rfd.fd:read(4096) - if chunk and #chunk > 0 then - if rfd.cb then - rfd.cb(chunk) - else - rfd.buf = rfd.buf or {} - rfd.buf[#rfd.buf + 1] = chunk - end - else - table.remove(pfds, i) - if rfd.buf then - rv[rfd.name] = table.concat(rfd.buf, "") - end - rfd.fd:close() - end - end - end - end - - if not nowait then - _, _, rv.code = nixio.waitpid(pid) - end - - return rv -end - - -user = {} - --- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } -user.getuser = nixio.getpw - -function user.getpasswd(username) - local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username) - local pwh = pwe and (pwe.pwdp or pwe.passwd) - if not pwh or #pwh < 1 then - return nil, pwe - else - return pwh, pwe - end -end - -function user.checkpasswd(username, pass) - local pwh, pwe = user.getpasswd(username) - if pwe then - return (pwh == nil or nixio.crypt(pass, pwh) == pwh) - end - return false -end - -function user.setpasswd(username, password) - return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{ - luci.util.shellquote(password), - luci.util.shellquote(password), - luci.util.shellquote(username) - }) -end - - -wifi = {} - -function wifi.getiwinfo(ifname) - local ntm = require "luci.model.network" - - ntm.init() - - local wnet = ntm:get_wifinet(ifname) - if wnet and wnet.iwinfo then - return wnet.iwinfo - end - - local wdev = ntm:get_wifidev(ifname) - if wdev and wdev.iwinfo then - return wdev.iwinfo - end - - return { ifname = ifname } -end - - -init = {} -init.dir = "/etc/init.d/" - -function init.names() - local names = { } - for name in fs.glob(init.dir.."*") do - names[#names+1] = fs.basename(name) - end - return names -end - -function init.index(name) - name = fs.basename(name) - if fs.access(init.dir..name) then - return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null" - %{ init.dir, name }) - end -end - -local function init_action(action, name) - name = fs.basename(name) - if fs.access(init.dir..name) then - return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action }) - end -end - -function init.enabled(name) - return (init_action("enabled", name) == 0) -end - -function init.enable(name) - return (init_action("enable", name) == 0) -end - -function init.disable(name) - return (init_action("disable", name) == 0) -end - -function init.start(name) - return (init_action("start", name) == 0) -end - -function init.stop(name) - return (init_action("stop", name) == 0) -end - -function init.restart(name) - return (init_action("restart", name) == 0) -end - -function init.reload(name) - return (init_action("reload", name) == 0) -end diff --git a/modules/luci-base/luasrc/sys.luadoc b/modules/luci-base/luasrc/sys.luadoc deleted file mode 100644 index c1e088eb23..0000000000 --- a/modules/luci-base/luasrc/sys.luadoc +++ /dev/null @@ -1,392 +0,0 @@ ----[[ -LuCI Linux and POSIX system utilities. -]] -module "luci.sys" - ----[[ -Execute a given shell command and return the error code - -@class function -@name call -@param ... Command to call -@return Error code of the command -]] - ----[[ -Execute a given shell command and capture its standard output - -@class function -@name exec -@param command Command to call -@return String containing the return the output of the command -]] - ----[[ -Retrieve information about currently mounted file systems. - -@class function -@name mounts -@return Table containing mount information -]] - ----[[ -Retrieve environment variables. If no variable is given then a table - -containing the whole environment is returned otherwise this function returns -the corresponding string value for the given name or nil if no such variable -exists. -@class function -@name getenv -@param var Name of the environment variable to retrieve (optional) -@return String containing the value of the specified variable -@return Table containing all variables if no variable name is given -]] - ----[[ -Get or set the current hostname. - -@class function -@name hostname -@param String containing a new hostname to set (optional) -@return String containing the system hostname -]] - ----[[ -Returns the contents of a documented referred by an URL. - -@class function -@name httpget -@param url The URL to retrieve -@param stream Return a stream instead of a buffer -@param target Directly write to target file name -@return String containing the contents of given the URL -]] - ----[[ -Initiate a system reboot. - -@class function -@name reboot -@return Return value of os.execute() -]] - ----[[ -Retrieves the output of the "logread" command. - -@class function -@name syslog -@return String containing the current log buffer -]] - ----[[ -Retrieves the output of the "dmesg" command. - -@class function -@name dmesg -@return String containing the current log buffer -]] - ----[[ -Generates a random id with specified length. - -@class function -@name uniqueid -@param bytes Number of bytes for the unique id -@return String containing hex encoded id -]] - ----[[ -Returns the current system uptime stats. - -@class function -@name uptime -@return String containing total uptime in seconds -]] - ----[[ -LuCI system utilities / network related functions. - -@class module -@name luci.sys.net -]] - ----[[ -Returns a two-dimensional table of mac address hints. - -@class function -@name net.mac_hints -@return Table of table containing known hosts from various sources. - Each entry contains the values in the following order: - [ "mac", "name" ] -]] - ----[[ -Returns a two-dimensional table of IPv4 address hints. - -@class function -@name net.ipv4_hints -@return Table of table containing known hosts from various sources. - Each entry contains the values in the following order: - [ "ip", "name" ] -]] - ----[[ -Returns a two-dimensional table of IPv6 address hints. - -@class function -@name net.ipv6_hints -@return Table of table containing known hosts from various sources. - Each entry contains the values in the following order: - [ "ip", "name" ] -]] - ----[[ -Returns a two-dimensional table of host hints. - -@class function -@name net.host_hints -@return Table of table containing known hosts from various sources, - indexed by mac address. Each subtable contains at least one - of the fields "name", "ipv4" or "ipv6". -]] - ----[[ -Returns conntrack information - -@class function -@name net.conntrack -@return Table with the currently tracked IP connections -]] - ----[[ -Determine the names of available network interfaces. - -@class function -@name net.devices -@return Table containing all current interface names -]] - ----[[ -LuCI system utilities / process related functions. - -@class module -@name luci.sys.process -]] - ----[[ -Get the current process id. - -@class function -@name process.info -@return Number containing the current pid -]] - ----[[ -Retrieve information about currently running processes. - -@class function -@name process.list -@return Table containing process information -]] - ----[[ -Set the gid of a process identified by given pid. - -@class function -@name process.setgroup -@param gid Number containing the Unix group id -@return Boolean indicating successful operation -@return String containing the error message if failed -@return Number containing the error code if failed -]] - ----[[ -Set the uid of a process identified by given pid. - -@class function -@name process.setuser -@param uid Number containing the Unix user id -@return Boolean indicating successful operation -@return String containing the error message if failed -@return Number containing the error code if failed -]] - ----[[ -Send a signal to a process identified by given pid. - -@class function -@name process.signal -@param pid Number containing the process id -@param sig Signal to send (default: 15 [SIGTERM]) -@return Boolean indicating successful operation -@return Number containing the error code if failed -]] - ----[[ -Execute a process, optionally capturing stdio. - -Executes the process specified by the given argv vector, e.g. -`{ "/bin/sh", "-c", "echo 1" }` and waits for it to terminate unless a true -value has been passed for the "nowait" parameter. - -When a function value is passed for the stdout or stderr arguments, the passed -function is repeatedly called for each chunk read from the corresponding stdio -stream. The read data is passed as string containing at most 4096 bytes at a -time. - -When a true, non-function value is passed for the stdout or stderr arguments, -the data of the corresponding stdio stream is read into an internal string -buffer and returned as "stdout" or "stderr" field respectively in the result -table. - -When a true value is passed to the nowait parameter, the function does not -await process termination but returns as soon as all captured stdio streams -have been closed or - if no streams are captured - immediately after launching -the process. - -@class function -@name process.exec -@param commend Table containing the argv vector to execute -@param stdout Callback function or boolean to indicate capturing (optional) -@param stderr Callback function or boolean to indicate capturing (optional) -@param nowait Don't wait for process termination when true (optional) -@return Table containing at least the fields "code" which holds the exit - status of the invoked process or "-1" on error and "pid", which - contains the process id assigned to the spawned process. When - stdout and/or stderr capturing has been requested, it additionally - contains "stdout" and "stderr" fields respectively, holding the - captured stdio data as string. -]] - ----[[ -LuCI system utilities / user related functions. - -@class module -@name luci.sys.user -]] - ----[[ -Retrieve user information for given uid. - -@class function -@name getuser -@param uid Number containing the Unix user id -@return Table containing the following fields: --- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } -]] - ----[[ -Retrieve the current user password hash. - -@class function -@name user.getpasswd -@param username String containing the username to retrieve the password for -@return String containing the hash or nil if no password is set. -@return Password database entry -]] - ----[[ -Test whether given string matches the password of a given system user. - -@class function -@name user.checkpasswd -@param username String containing the Unix user name -@param pass String containing the password to compare -@return Boolean indicating whether the passwords are equal -]] - ----[[ -Change the password of given user. - -@class function -@name user.setpasswd -@param username String containing the Unix user name -@param password String containing the password to compare -@return Number containing 0 on success and >= 1 on error -]] - ----[[ -LuCI system utilities / wifi related functions. - -@class module -@name luci.sys.wifi -]] - ----[[ -Get wireless information for given interface. - -@class function -@name wifi.getiwinfo -@param ifname String containing the interface name -@return A wrapped iwinfo object instance -]] - ----[[ -LuCI system utilities / init related functions. - -@class module -@name luci.sys.init -]] - ----[[ -Get the names of all installed init scripts - -@class function -@name init.names -@return Table containing the names of all inistalled init scripts -]] - ----[[ -Get the index of he given init script - -@class function -@name init.index -@param name Name of the init script -@return Numeric index value -]] - ----[[ -Test whether the given init script is enabled - -@class function -@name init.enabled -@param name Name of the init script -@return Boolean indicating whether init is enabled -]] - ----[[ -Enable the given init script - -@class function -@name init.enable -@param name Name of the init script -@return Boolean indicating success -]] - ----[[ -Disable the given init script - -@class function -@name init.disable -@param name Name of the init script -@return Boolean indicating success -]] - ----[[ -Start the given init script - -@class function -@name init.start -@param name Name of the init script -@return Boolean indicating success -]] - ----[[ -Stop the given init script - -@class function -@name init.stop -@param name Name of the init script -@return Boolean indicating success -]] - diff --git a/modules/luci-base/luasrc/sys/zoneinfo.lua b/modules/luci-base/luasrc/sys/zoneinfo.lua deleted file mode 100644 index aa054a246f..0000000000 --- a/modules/luci-base/luasrc/sys/zoneinfo.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -local setmetatable, require, rawget, rawset = setmetatable, require, rawget, rawset - -module "luci.sys.zoneinfo" - -setmetatable(_M, { - __index = function(t, k) - if k == "TZ" and not rawget(t, k) then - local m = require "luci.sys.zoneinfo.tzdata" - rawset(t, k, rawget(m, k)) - elseif k == "OFFSET" and not rawget(t, k) then - local m = require "luci.sys.zoneinfo.tzoffset" - rawset(t, k, rawget(m, k)) - end - - return rawget(t, k) - end -}) diff --git a/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua b/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua deleted file mode 100644 index 3ef2f4caf4..0000000000 --- a/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua +++ /dev/null @@ -1,455 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -module "luci.sys.zoneinfo.tzdata" - -TZ = { - { 'Africa/Abidjan', 'GMT0' }, - { 'Africa/Accra', 'GMT0' }, - { 'Africa/Addis Ababa', 'EAT-3' }, - { 'Africa/Algiers', 'CET-1' }, - { 'Africa/Asmara', 'EAT-3' }, - { 'Africa/Bamako', 'GMT0' }, - { 'Africa/Bangui', 'WAT-1' }, - { 'Africa/Banjul', 'GMT0' }, - { 'Africa/Bissau', 'GMT0' }, - { 'Africa/Blantyre', 'CAT-2' }, - { 'Africa/Brazzaville', 'WAT-1' }, - { 'Africa/Bujumbura', 'CAT-2' }, - { 'Africa/Cairo', 'EET-2' }, - { 'Africa/Casablanca', '<+01>-1' }, - { 'Africa/Ceuta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Africa/Conakry', 'GMT0' }, - { 'Africa/Dakar', 'GMT0' }, - { 'Africa/Dar es Salaam', 'EAT-3' }, - { 'Africa/Djibouti', 'EAT-3' }, - { 'Africa/Douala', 'WAT-1' }, - { 'Africa/El Aaiun', '<+01>-1' }, - { 'Africa/Freetown', 'GMT0' }, - { 'Africa/Gaborone', 'CAT-2' }, - { 'Africa/Harare', 'CAT-2' }, - { 'Africa/Johannesburg', 'SAST-2' }, - { 'Africa/Juba', 'CAT-2' }, - { 'Africa/Kampala', 'EAT-3' }, - { 'Africa/Khartoum', 'CAT-2' }, - { 'Africa/Kigali', 'CAT-2' }, - { 'Africa/Kinshasa', 'WAT-1' }, - { 'Africa/Lagos', 'WAT-1' }, - { 'Africa/Libreville', 'WAT-1' }, - { 'Africa/Lome', 'GMT0' }, - { 'Africa/Luanda', 'WAT-1' }, - { 'Africa/Lubumbashi', 'CAT-2' }, - { 'Africa/Lusaka', 'CAT-2' }, - { 'Africa/Malabo', 'WAT-1' }, - { 'Africa/Maputo', 'CAT-2' }, - { 'Africa/Maseru', 'SAST-2' }, - { 'Africa/Mbabane', 'SAST-2' }, - { 'Africa/Mogadishu', 'EAT-3' }, - { 'Africa/Monrovia', 'GMT0' }, - { 'Africa/Nairobi', 'EAT-3' }, - { 'Africa/Ndjamena', 'WAT-1' }, - { 'Africa/Niamey', 'WAT-1' }, - { 'Africa/Nouakchott', 'GMT0' }, - { 'Africa/Ouagadougou', 'GMT0' }, - { 'Africa/Porto-Novo', 'WAT-1' }, - { 'Africa/Sao Tome', 'GMT0' }, - { 'Africa/Tripoli', 'EET-2' }, - { 'Africa/Tunis', 'CET-1' }, - { 'Africa/Windhoek', 'CAT-2' }, - { 'America/Adak', 'HST10HDT,M3.2.0,M11.1.0' }, - { 'America/Anchorage', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Anguilla', 'AST4' }, - { 'America/Antigua', 'AST4' }, - { 'America/Araguaina', '<-03>3' }, - { 'America/Argentina/Buenos Aires', '<-03>3' }, - { 'America/Argentina/Catamarca', '<-03>3' }, - { 'America/Argentina/Cordoba', '<-03>3' }, - { 'America/Argentina/Jujuy', '<-03>3' }, - { 'America/Argentina/La Rioja', '<-03>3' }, - { 'America/Argentina/Mendoza', '<-03>3' }, - { 'America/Argentina/Rio Gallegos', '<-03>3' }, - { 'America/Argentina/Salta', '<-03>3' }, - { 'America/Argentina/San Juan', '<-03>3' }, - { 'America/Argentina/San Luis', '<-03>3' }, - { 'America/Argentina/Tucuman', '<-03>3' }, - { 'America/Argentina/Ushuaia', '<-03>3' }, - { 'America/Aruba', 'AST4' }, - { 'America/Asuncion', '<-04>4<-03>,M10.1.0/0,M3.4.0/0' }, - { 'America/Atikokan', 'EST5' }, - { 'America/Bahia', '<-03>3' }, - { 'America/Bahia Banderas', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Barbados', 'AST4' }, - { 'America/Belem', '<-03>3' }, - { 'America/Belize', 'CST6' }, - { 'America/Blanc-Sablon', 'AST4' }, - { 'America/Boa Vista', '<-04>4' }, - { 'America/Bogota', '<-05>5' }, - { 'America/Boise', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Cambridge Bay', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Campo Grande', '<-04>4' }, - { 'America/Cancun', 'EST5' }, - { 'America/Caracas', '<-04>4' }, - { 'America/Cayenne', '<-03>3' }, - { 'America/Cayman', 'EST5' }, - { 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Chihuahua', 'MST7MDT,M4.1.0,M10.5.0' }, - { 'America/Costa Rica', 'CST6' }, - { 'America/Creston', 'MST7' }, - { 'America/Cuiaba', '<-04>4' }, - { 'America/Curacao', 'AST4' }, - { 'America/Danmarkshavn', 'GMT0' }, - { 'America/Dawson', 'MST7' }, - { 'America/Dawson Creek', 'MST7' }, - { 'America/Denver', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Detroit', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Dominica', 'AST4' }, - { 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Eirunepe', '<-05>5' }, - { 'America/El Salvador', 'CST6' }, - { 'America/Fort Nelson', 'MST7' }, - { 'America/Fortaleza', '<-03>3' }, - { 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Goose Bay', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Grand Turk', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Grenada', 'AST4' }, - { 'America/Guadeloupe', 'AST4' }, - { 'America/Guatemala', 'CST6' }, - { 'America/Guayaquil', '<-05>5' }, - { 'America/Guyana', '<-04>4' }, - { 'America/Halifax', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Havana', 'CST5CDT,M3.2.0/0,M11.1.0/1' }, - { 'America/Hermosillo', 'MST7' }, - { 'America/Indiana/Indianapolis', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Knox', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Marengo', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Petersburg', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Tell City', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Vevay', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Vincennes', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Winamac', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Inuvik', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Iqaluit', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Jamaica', 'EST5' }, - { 'America/Juneau', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Kentucky/Louisville', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Kentucky/Monticello', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Kralendijk', 'AST4' }, - { 'America/La Paz', '<-04>4' }, - { 'America/Lima', '<-05>5' }, - { 'America/Los Angeles', 'PST8PDT,M3.2.0,M11.1.0' }, - { 'America/Lower Princes', 'AST4' }, - { 'America/Maceio', '<-03>3' }, - { 'America/Managua', 'CST6' }, - { 'America/Manaus', '<-04>4' }, - { 'America/Marigot', 'AST4' }, - { 'America/Martinique', 'AST4' }, - { 'America/Matamoros', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Mazatlan', 'MST7MDT,M4.1.0,M10.5.0' }, - { 'America/Menominee', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Merida', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Metlakatla', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Mexico City', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Miquelon', '<-03>3<-02>,M3.2.0,M11.1.0' }, - { 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Monterrey', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Montevideo', '<-03>3' }, - { 'America/Montserrat', 'AST4' }, - { 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Nipigon', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Nome', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Noronha', '<-02>2' }, - { 'America/North Dakota/Beulah', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/North Dakota/Center', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/North Dakota/New Salem', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Nuuk', '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1' }, - { 'America/Ojinaga', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Panama', 'EST5' }, - { 'America/Pangnirtung', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Paramaribo', '<-03>3' }, - { 'America/Phoenix', 'MST7' }, - { 'America/Port of Spain', 'AST4' }, - { 'America/Port-au-Prince', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Porto Velho', '<-04>4' }, - { 'America/Puerto Rico', 'AST4' }, - { 'America/Punta Arenas', '<-03>3' }, - { 'America/Rainy River', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Rankin Inlet', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Recife', '<-03>3' }, - { 'America/Regina', 'CST6' }, - { 'America/Resolute', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Rio Branco', '<-05>5' }, - { 'America/Santarem', '<-03>3' }, - { 'America/Santiago', '<-04>4<-03>,M9.1.6/24,M4.1.6/24' }, - { 'America/Santo Domingo', 'AST4' }, - { 'America/Sao Paulo', '<-03>3' }, - { 'America/Scoresbysund', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, - { 'America/Sitka', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/St Barthelemy', 'AST4' }, - { 'America/St Johns', 'NST3:30NDT,M3.2.0,M11.1.0' }, - { 'America/St Kitts', 'AST4' }, - { 'America/St Lucia', 'AST4' }, - { 'America/St Thomas', 'AST4' }, - { 'America/St Vincent', 'AST4' }, - { 'America/Swift Current', 'CST6' }, - { 'America/Tegucigalpa', 'CST6' }, - { 'America/Thule', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Thunder Bay', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Tijuana', 'PST8PDT,M3.2.0,M11.1.0' }, - { 'America/Toronto', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Tortola', 'AST4' }, - { 'America/Vancouver', 'PST8PDT,M3.2.0,M11.1.0' }, - { 'America/Whitehorse', 'MST7' }, - { 'America/Winnipeg', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Yakutat', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Yellowknife', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'Antarctica/Casey', '<+11>-11' }, - { 'Antarctica/Davis', '<+07>-7' }, - { 'Antarctica/DumontDUrville', '<+10>-10' }, - { 'Antarctica/Macquarie', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Antarctica/Mawson', '<+05>-5' }, - { 'Antarctica/McMurdo', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, - { 'Antarctica/Palmer', '<-03>3' }, - { 'Antarctica/Rothera', '<-03>3' }, - { 'Antarctica/Syowa', '<+03>-3' }, - { 'Antarctica/Troll', '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3' }, - { 'Antarctica/Vostok', '<+06>-6' }, - { 'Arctic/Longyearbyen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Asia/Aden', '<+03>-3' }, - { 'Asia/Almaty', '<+06>-6' }, - { 'Asia/Amman', '<+03>-3' }, - { 'Asia/Anadyr', '<+12>-12' }, - { 'Asia/Aqtau', '<+05>-5' }, - { 'Asia/Aqtobe', '<+05>-5' }, - { 'Asia/Ashgabat', '<+05>-5' }, - { 'Asia/Atyrau', '<+05>-5' }, - { 'Asia/Baghdad', '<+03>-3' }, - { 'Asia/Bahrain', '<+03>-3' }, - { 'Asia/Baku', '<+04>-4' }, - { 'Asia/Bangkok', '<+07>-7' }, - { 'Asia/Barnaul', '<+07>-7' }, - { 'Asia/Beirut', 'EET-2EEST,M3.5.0/0,M10.5.0/0' }, - { 'Asia/Bishkek', '<+06>-6' }, - { 'Asia/Brunei', '<+08>-8' }, - { 'Asia/Chita', '<+09>-9' }, - { 'Asia/Choibalsan', '<+08>-8' }, - { 'Asia/Colombo', '<+0530>-5:30' }, - { 'Asia/Damascus', '<+03>-3' }, - { 'Asia/Dhaka', '<+06>-6' }, - { 'Asia/Dili', '<+09>-9' }, - { 'Asia/Dubai', '<+04>-4' }, - { 'Asia/Dushanbe', '<+05>-5' }, - { 'Asia/Famagusta', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Asia/Gaza', 'EET-2EEST,M3.4.4/50,M10.4.4/50' }, - { 'Asia/Hebron', 'EET-2EEST,M3.4.4/50,M10.4.4/50' }, - { 'Asia/Ho Chi Minh', '<+07>-7' }, - { 'Asia/Hong Kong', 'HKT-8' }, - { 'Asia/Hovd', '<+07>-7' }, - { 'Asia/Irkutsk', '<+08>-8' }, - { 'Asia/Jakarta', 'WIB-7' }, - { 'Asia/Jayapura', 'WIT-9' }, - { 'Asia/Jerusalem', 'IST-2IDT,M3.4.4/26,M10.5.0' }, - { 'Asia/Kabul', '<+0430>-4:30' }, - { 'Asia/Kamchatka', '<+12>-12' }, - { 'Asia/Karachi', 'PKT-5' }, - { 'Asia/Kathmandu', '<+0545>-5:45' }, - { 'Asia/Khandyga', '<+09>-9' }, - { 'Asia/Kolkata', 'IST-5:30' }, - { 'Asia/Krasnoyarsk', '<+07>-7' }, - { 'Asia/Kuala Lumpur', '<+08>-8' }, - { 'Asia/Kuching', '<+08>-8' }, - { 'Asia/Kuwait', '<+03>-3' }, - { 'Asia/Macau', 'CST-8' }, - { 'Asia/Magadan', '<+11>-11' }, - { 'Asia/Makassar', 'WITA-8' }, - { 'Asia/Manila', 'PST-8' }, - { 'Asia/Muscat', '<+04>-4' }, - { 'Asia/Nicosia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Asia/Novokuznetsk', '<+07>-7' }, - { 'Asia/Novosibirsk', '<+07>-7' }, - { 'Asia/Omsk', '<+06>-6' }, - { 'Asia/Oral', '<+05>-5' }, - { 'Asia/Phnom Penh', '<+07>-7' }, - { 'Asia/Pontianak', 'WIB-7' }, - { 'Asia/Pyongyang', 'KST-9' }, - { 'Asia/Qatar', '<+03>-3' }, - { 'Asia/Qostanay', '<+06>-6' }, - { 'Asia/Qyzylorda', '<+05>-5' }, - { 'Asia/Riyadh', '<+03>-3' }, - { 'Asia/Sakhalin', '<+11>-11' }, - { 'Asia/Samarkand', '<+05>-5' }, - { 'Asia/Seoul', 'KST-9' }, - { 'Asia/Shanghai', 'CST-8' }, - { 'Asia/Singapore', '<+08>-8' }, - { 'Asia/Srednekolymsk', '<+11>-11' }, - { 'Asia/Taipei', 'CST-8' }, - { 'Asia/Tashkent', '<+05>-5' }, - { 'Asia/Tbilisi', '<+04>-4' }, - { 'Asia/Tehran', '<+0330>-3:30' }, - { 'Asia/Thimphu', '<+06>-6' }, - { 'Asia/Tokyo', 'JST-9' }, - { 'Asia/Tomsk', '<+07>-7' }, - { 'Asia/Ulaanbaatar', '<+08>-8' }, - { 'Asia/Urumqi', '<+06>-6' }, - { 'Asia/Ust-Nera', '<+10>-10' }, - { 'Asia/Vientiane', '<+07>-7' }, - { 'Asia/Vladivostok', '<+10>-10' }, - { 'Asia/Yakutsk', '<+09>-9' }, - { 'Asia/Yangon', '<+0630>-6:30' }, - { 'Asia/Yekaterinburg', '<+05>-5' }, - { 'Asia/Yerevan', '<+04>-4' }, - { 'Atlantic/Azores', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, - { 'Atlantic/Bermuda', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'Atlantic/Canary', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Atlantic/Cape Verde', '<-01>1' }, - { 'Atlantic/Faroe', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Atlantic/Madeira', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Atlantic/Reykjavik', 'GMT0' }, - { 'Atlantic/South Georgia', '<-02>2' }, - { 'Atlantic/St Helena', 'GMT0' }, - { 'Atlantic/Stanley', '<-03>3' }, - { 'Australia/Adelaide', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Brisbane', 'AEST-10' }, - { 'Australia/Broken Hill', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Darwin', 'ACST-9:30' }, - { 'Australia/Eucla', '<+0845>-8:45' }, - { 'Australia/Hobart', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Lindeman', 'AEST-10' }, - { 'Australia/Lord Howe', '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0' }, - { 'Australia/Melbourne', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Perth', 'AWST-8' }, - { 'Australia/Sydney', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Etc/GMT', 'GMT0' }, - { 'Etc/GMT+1', '<-01>1' }, - { 'Etc/GMT+10', '<-10>10' }, - { 'Etc/GMT+11', '<-11>11' }, - { 'Etc/GMT+12', '<-12>12' }, - { 'Etc/GMT+2', '<-02>2' }, - { 'Etc/GMT+3', '<-03>3' }, - { 'Etc/GMT+4', '<-04>4' }, - { 'Etc/GMT+5', '<-05>5' }, - { 'Etc/GMT+6', '<-06>6' }, - { 'Etc/GMT+7', '<-07>7' }, - { 'Etc/GMT+8', '<-08>8' }, - { 'Etc/GMT+9', '<-09>9' }, - { 'Etc/GMT-1', '<+01>-1' }, - { 'Etc/GMT-10', '<+10>-10' }, - { 'Etc/GMT-11', '<+11>-11' }, - { 'Etc/GMT-12', '<+12>-12' }, - { 'Etc/GMT-13', '<+13>-13' }, - { 'Etc/GMT-14', '<+14>-14' }, - { 'Etc/GMT-2', '<+02>-2' }, - { 'Etc/GMT-3', '<+03>-3' }, - { 'Etc/GMT-4', '<+04>-4' }, - { 'Etc/GMT-5', '<+05>-5' }, - { 'Etc/GMT-6', '<+06>-6' }, - { 'Etc/GMT-7', '<+07>-7' }, - { 'Etc/GMT-8', '<+08>-8' }, - { 'Etc/GMT-9', '<+09>-9' }, - { 'Europe/Amsterdam', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Andorra', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Astrakhan', '<+04>-4' }, - { 'Europe/Athens', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Belgrade', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Berlin', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Bratislava', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Brussels', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Busingen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Chisinau', 'EET-2EEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Dublin', 'IST-1GMT0,M10.5.0,M3.5.0/1' }, - { 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Guernsey', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Helsinki', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Isle of Man', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Istanbul', '<+03>-3' }, - { 'Europe/Jersey', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Kaliningrad', 'EET-2' }, - { 'Europe/Kirov', '<+03>-3' }, - { 'Europe/Kyiv', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Lisbon', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Europe/Ljubljana', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/London', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Luxembourg', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Madrid', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Malta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Mariehamn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Minsk', '<+03>-3' }, - { 'Europe/Monaco', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Moscow', 'MSK-3' }, - { 'Europe/Oslo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Paris', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Podgorica', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Prague', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Riga', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Rome', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Samara', '<+04>-4' }, - { 'Europe/San Marino', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Sarajevo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Saratov', '<+04>-4' }, - { 'Europe/Simferopol', 'MSK-3' }, - { 'Europe/Skopje', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Sofia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Stockholm', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Tallinn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Tirane', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Ulyanovsk', '<+04>-4' }, - { 'Europe/Vaduz', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Vatican', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Vienna', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Vilnius', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Volgograd', '<+03>-3' }, - { 'Europe/Warsaw', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Zagreb', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Zurich', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Indian/Antananarivo', 'EAT-3' }, - { 'Indian/Chagos', '<+06>-6' }, - { 'Indian/Christmas', '<+07>-7' }, - { 'Indian/Cocos', '<+0630>-6:30' }, - { 'Indian/Comoro', 'EAT-3' }, - { 'Indian/Kerguelen', '<+05>-5' }, - { 'Indian/Mahe', '<+04>-4' }, - { 'Indian/Maldives', '<+05>-5' }, - { 'Indian/Mauritius', '<+04>-4' }, - { 'Indian/Mayotte', 'EAT-3' }, - { 'Indian/Reunion', '<+04>-4' }, - { 'Pacific/Apia', '<+13>-13' }, - { 'Pacific/Auckland', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, - { 'Pacific/Bougainville', '<+11>-11' }, - { 'Pacific/Chatham', '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45' }, - { 'Pacific/Chuuk', '<+10>-10' }, - { 'Pacific/Easter', '<-06>6<-05>,M9.1.6/22,M4.1.6/22' }, - { 'Pacific/Efate', '<+11>-11' }, - { 'Pacific/Fakaofo', '<+13>-13' }, - { 'Pacific/Fiji', '<+12>-12<+13>,M11.2.0,M1.2.3/99' }, - { 'Pacific/Funafuti', '<+12>-12' }, - { 'Pacific/Galapagos', '<-06>6' }, - { 'Pacific/Gambier', '<-09>9' }, - { 'Pacific/Guadalcanal', '<+11>-11' }, - { 'Pacific/Guam', 'ChST-10' }, - { 'Pacific/Honolulu', 'HST10' }, - { 'Pacific/Kanton', '<+13>-13' }, - { 'Pacific/Kiritimati', '<+14>-14' }, - { 'Pacific/Kosrae', '<+11>-11' }, - { 'Pacific/Kwajalein', '<+12>-12' }, - { 'Pacific/Majuro', '<+12>-12' }, - { 'Pacific/Marquesas', '<-0930>9:30' }, - { 'Pacific/Midway', 'SST11' }, - { 'Pacific/Nauru', '<+12>-12' }, - { 'Pacific/Niue', '<-11>11' }, - { 'Pacific/Norfolk', '<+11>-11<+12>,M10.1.0,M4.1.0/3' }, - { 'Pacific/Noumea', '<+11>-11' }, - { 'Pacific/Pago Pago', 'SST11' }, - { 'Pacific/Palau', '<+09>-9' }, - { 'Pacific/Pitcairn', '<-08>8' }, - { 'Pacific/Pohnpei', '<+11>-11' }, - { 'Pacific/Port Moresby', '<+10>-10' }, - { 'Pacific/Rarotonga', '<-10>10' }, - { 'Pacific/Saipan', 'ChST-10' }, - { 'Pacific/Tahiti', '<-10>10' }, - { 'Pacific/Tarawa', '<+12>-12' }, - { 'Pacific/Tongatapu', '<+13>-13' }, - { 'Pacific/Wake', '<+12>-12' }, - { 'Pacific/Wallis', '<+12>-12' }, -} diff --git a/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua b/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua deleted file mode 100644 index caee1d2c1c..0000000000 --- a/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua +++ /dev/null @@ -1,46 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -module "luci.sys.zoneinfo.tzoffset" - -OFFSET = { - gmt = 0, -- GMT - eat = 10800, -- EAT - cet = 3600, -- CET - wat = 3600, -- WAT - cat = 7200, -- CAT - eet = 7200, -- EET - sast = 7200, -- SAST - hst = -36000, -- HST - hdt = -32400, -- HDT - akst = -32400, -- AKST - akdt = -28800, -- AKDT - ast = -14400, -- AST - est = -18000, -- EST - cst = -21600, -- CST - cdt = -18000, -- CDT - mst = -25200, -- MST - mdt = -21600, -- MDT - pst = -28800, -- PST - pdt = -25200, -- PDT - nst = -12600, -- NST - ndt = -9000, -- NDT - aest = 36000, -- AEST - aedt = 39600, -- AEDT - nzst = 43200, -- NZST - nzdt = 46800, -- NZDT - hkt = 28800, -- HKT - wib = 25200, -- WIB - wit = 32400, -- WIT - ist = 7200, -- IST - idt = 10800, -- IDT - pkt = 18000, -- PKT - wita = 28800, -- WITA - kst = 32400, -- KST - jst = 32400, -- JST - wet = 0, -- WET - acst = 34200, -- ACST - acdt = 37800, -- ACDT - awst = 28800, -- AWST - msk = 10800, -- MSK - sst = -39600, -- SST -} diff --git a/modules/luci-base/luasrc/template.lua b/modules/luci-base/luasrc/template.lua deleted file mode 100644 index 3955bd76f3..0000000000 --- a/modules/luci-base/luasrc/template.lua +++ /dev/null @@ -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 diff --git a/modules/luci-base/luasrc/version.lua b/modules/luci-base/luasrc/version.lua deleted file mode 100644 index 8af2e80619..0000000000 --- a/modules/luci-base/luasrc/version.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -module "luci.version" - -distname = "Host System" -distversion = "SDK" - -luciname = "LuCI" -luciversion = "SVN" diff --git a/modules/luci-base/luasrc/view/empty_node_placeholder.htm b/modules/luci-base/luasrc/view/empty_node_placeholder.htm deleted file mode 100644 index b7e276b960..0000000000 --- a/modules/luci-base/luasrc/view/empty_node_placeholder.htm +++ /dev/null @@ -1,11 +0,0 @@ -<%# - Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> - Copyright 2018 Daniel F. Dickinson <cshored@thecshore.com> - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -<p>Component not present.</p> - -<%+footer%> diff --git a/modules/luci-base/luasrc/view/error404.htm b/modules/luci-base/luasrc/view/error404.htm deleted file mode 100644 index ff151d1834..0000000000 --- a/modules/luci-base/luasrc/view/error404.htm +++ /dev/null @@ -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%> diff --git a/modules/luci-base/luasrc/view/error500.htm b/modules/luci-base/luasrc/view/error500.htm deleted file mode 100644 index 34a52cda84..0000000000 --- a/modules/luci-base/luasrc/view/error500.htm +++ /dev/null @@ -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%> diff --git a/modules/luci-base/luasrc/view/footer.htm b/modules/luci-base/luasrc/view/footer.htm deleted file mode 100644 index ba14ec8678..0000000000 --- a/modules/luci-base/luasrc/view/footer.htm +++ /dev/null @@ -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") -%> diff --git a/modules/luci-base/luasrc/view/header.htm b/modules/luci-base/luasrc/view/header.htm deleted file mode 100644 index cffe9482ca..0000000000 --- a/modules/luci-base/luasrc/view/header.htm +++ /dev/null @@ -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> diff --git a/modules/luci-base/luasrc/view/indexer.htm b/modules/luci-base/luasrc/view/indexer.htm deleted file mode 100644 index 28fc3debc3..0000000000 --- a/modules/luci-base/luasrc/view/indexer.htm +++ /dev/null @@ -1,7 +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. --%> - -<% include("themes/" .. theme .. "/indexer") %>
\ No newline at end of file diff --git a/modules/luci-base/luasrc/view/sysauth.htm b/modules/luci-base/luasrc/view/sysauth.htm deleted file mode 100644 index 797c87a72e..0000000000 --- a/modules/luci-base/luasrc/view/sysauth.htm +++ /dev/null @@ -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%> diff --git a/modules/luci-base/luasrc/view/view.htm b/modules/luci-base/luasrc/view/view.htm deleted file mode 100644 index b451e8cfbf..0000000000 --- a/modules/luci-base/luasrc/view/view.htm +++ /dev/null @@ -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%> diff --git a/modules/luci-base/luasrc/xml.lua b/modules/luci-base/luasrc/xml.lua deleted file mode 100644 index 30b37210bd..0000000000 --- a/modules/luci-base/luasrc/xml.lua +++ /dev/null @@ -1,26 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -local tparser = require "luci.template.parser" -local string = require "string" - -local tostring = tostring - -module "luci.xml" - --- --- String and data manipulation routines --- - -function pcdata(value) - return value and tparser.pcdata(tostring(value)) -end - -function striptags(value) - return value and tparser.striptags(tostring(value)) -end - - --- also register functions above in the central string class for convenience -string.pcdata = pcdata -string.striptags = striptags diff --git a/modules/luci-base/luasrc/xml.luadoc b/modules/luci-base/luasrc/xml.luadoc deleted file mode 100644 index 58de533966..0000000000 --- a/modules/luci-base/luasrc/xml.luadoc +++ /dev/null @@ -1,23 +0,0 @@ ----[[ -LuCI utility functions. -]] -module "luci.xml" - ----[[ -Create valid XML PCDATA from given string. - -@class function -@name pcdata -@param value String value containing the data to escape -@return String value containing the escaped data -]] - ----[[ -Strip HTML tags from given string. - -@class function -@name striptags -@param value String containing the HTML text -@return String with HTML tags stripped of -]] - diff --git a/modules/luci-base/root/usr/libexec/rpcd/luci b/modules/luci-base/root/usr/libexec/rpcd/luci deleted file mode 100755 index f124512f59..0000000000 --- a/modules/luci-base/root/usr/libexec/rpcd/luci +++ /dev/null @@ -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 diff --git a/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json b/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json index 605c7ab777..000c368151 100644 --- a/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json +++ b/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json @@ -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" }, diff --git a/modules/luci-base/root/usr/share/rpcd/ucode/luci b/modules/luci-base/root/usr/share/rpcd/ucode/luci new file mode 100644 index 0000000000..794676abc6 --- /dev/null +++ b/modules/luci-base/root/usr/share/rpcd/ucode/luci @@ -0,0 +1,512 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +'use strict'; + +import { stdin, access, dirname, basename, open, popen, glob, lsdir, readfile, readlink, error } from 'fs'; +import { cursor } from 'uci'; + +import { init_list, init_index, init_enabled, init_action, conntrack_list, process_list } from 'luci.sys'; +import { statvfs } from 'luci.core'; + +import timezones from 'luci.zoneinfo'; + + +function shellquote(s) { + return `'${replace(s, "'", "'\\''")}'`; +} + +const methods = { + getInitList: { + args: { name: 'name' }, + call: function(request) { + let scripts = {}; + + for (let name in filter(init_list(), i => !request.args.name || i == request.args.name)) { + let idx = init_index(name); + + scripts[name] = { + index: idx[0], + stop: idx[1], + enabled: init_enabled(name) + }; + } + + return length(scripts) ? scripts : { error: 'No such init script' }; + } + }, + + setInitAction: { + args: { name: 'name', action: 'action' }, + call: function(request) { + switch (request.args.action) { + case 'enable': + case 'disable': + case 'start': + case 'stop': + case 'restart': + case 'reload': + const rc = init_action(request.args.name, request.args.action); + + if (rc === false) + return { error: 'No such init script' }; + + return { result: rc == 0 }; + + default: + return { error: 'Invalid action' }; + } + } + }, + + getLocaltime: { + call: function(request) { + return { result: time() }; + } + }, + + setLocaltime: { + args: { localtime: 0 }, + call: function(request) { + let t = localtime(request.args.localtime); + + if (t) { + system(sprintf('date -s "%04d-%02d-%02d %02d:%02d:%02d" >/dev/null', t.year, t.mon, t.mday, t.hour, t.min, t.sec)); + system('/etc/init.d/sysfixtime restart >/dev/null'); + } + + return { result: request.args.localtime }; + } + }, + + getTimezones: { + call: function(request) { + let tz = trim(readfile('/etc/TZ')); + let zn = cursor()?.get?.('system', '@system[0]', 'zonename'); + let result = {}; + + for (let zone, tzstring in timezones) { + result[zone] = { tzstring }; + + if (zn == zone) + result[zone].active = true; + }; + + return result; + } + }, + + getLEDs: { + call: function() { + let result = {}; + + for (let led in lsdir('/sys/class/leds')) { + let s; + + result[led] = { triggers: [] }; + + s = trim(readfile(`/sys/class/leds/${led}/trigger`)); + for (let trigger in split(s, ' ')) { + push(result[led].triggers, trim(trigger, '[]')); + + if (trigger != result[led].triggers[-1]) + result[led].active_trigger = result[led].triggers[-1]; + } + + s = readfile(`/sys/class/leds/${led}/brightness`); + result[led].brightness = +s; + + s = readfile(`/sys/class/leds/${led}/max_brightness`); + result[led].max_brightness = +s; + } + + return result; + } + }, + + getUSBDevices: { + call: function() { + let result = { devices: [], ports: [] }; + + for (let path in glob('/sys/bus/usb/devices/[0-9]*/manufacturer')) { + let id = basename(dirname(path)); + + push(result.devices, { + id, + vid: trim(readfile(`/sys/bus/usb/devices/${id}/idVendor`)), + pid: trim(readfile(`/sys/bus/usb/devices/${id}/idProduct`)), + vendor: trim(readfile(path)), + product: trim(readfile(`/sys/bus/usb/devices/${id}/product`)), + speed: +readfile(`/sys/bus/usb/devices/${id}/speed`) + }); + } + + for (let path in glob('/sys/bus/usb/devices/*/*-port[0-9]*')) { + let port = basename(path); + let link = readlink(`${path}/device`); + + push(result.ports, { + port, + device: basename(link) + }); + } + + return result; + } + }, + + getConntrackHelpers: { + call: function() { + const uci = cursor(); + let helpers = []; + + uci.load('/usr/share/firewall4/helpers'); + uci.load('/usr/share/fw3/helpers.conf'); + + uci.foreach('helpers', 'helper', (s) => { + push(helpers, { + name: s.name, + description: s.description, + module: s.module, + family: s.family, + proto: s.proto, + port: s.port + }); + }); + + return { result: helpers }; + } + }, + + getFeatures: { + call: function() { + let result = { + firewall: access('/sbin/fw3') == true, + firewall4: access('/sbin/fw4') == true, + opkg: access('/bin/opkg') == true, + offloading: access('/sys/module/xt_FLOWOFFLOAD/refcnt') == true || access('/sys/module/nft_flow_offload/refcnt') == true, + br2684ctl: access('/usr/sbin/br2684ctl') == true, + swconfig: access('/sbin/swconfig') == true, + odhcpd: access('/usr/sbin/odhcpd') == true, + zram: access('/sys/class/zram-control') == true, + sysntpd: readlink('/usr/sbin/ntpd') != null, + ipv6: access('/proc/net/ipv6_route') == true, + dropbear: access('/usr/sbin/dropbear') == true, + cabundle: access('/etc/ssl/certs/ca-certificates.crt') == true, + relayd: access('/usr/sbin/relayd') == true, + }; + + const wifi_features = [ 'eap', '11n', '11ac', '11r', 'acs', 'sae', 'owe', 'suiteb192', 'wep', 'wps' ]; + + if (access('/usr/sbin/hostapd')) { + result.hostapd = { cli: access('/usr/sbin/hostapd_cli') == true }; + + for (let feature in wifi_features) + result.hostapd[feature] = system(`/usr/sbin/hostapd -v${feature} >/dev/null 2>/dev/null`) == 0; + } + + if (access('/usr/sbin/wpa_supplicant')) { + result.wpasupplicant = { cli: access('/usr/sbin/wpa_cli') == true }; + + for (let feature in wifi_features) + result.wpasupplicant[feature] = system(`/usr/sbin/wpa_supplicant -v${feature} >/dev/null 2>/dev/null`) == 0; + } + + let fd = popen('dnsmasq --version 2>/dev/null'); + + if (fd) { + const m = match(fd.read('all'), /^Compile time options: (.+)$/s); + + for (let opt in split(m?.[1], ' ')) { + let f = replace(opt, 'no-', '', 1); + + result.dnsmasq ??= {}; + result.dnsmasq[lc(f)] = (f == opt); + } + + fd.close(); + } + + fd = popen('ipset --help 2>/dev/null'); + + if (fd) { + for (let line = fd.read('line'), flag = false; length(line); line = fd.read('line')) { + if (line == 'Supported set types:\n') { + flag = true; + } + else if (flag) { + const m = match(line, /^ +([\w:,]+)\t+([0-9]+)\t/); + + if (m) { + result.ipset ??= {}; + result.ipset[m[1]] ??= +m[2]; + } + } + } + + fd.close(); + } + + return result; + } + }, + + getSwconfigFeatures: { + args: { switch: 'switch0' }, + call: function(request) { + // Parse some common switch properties from swconfig help output. + const swc = popen(`swconfig dev ${shellquote(request.args.switch)} help 2>/dev/null`); + + if (swc) { + let is_port_attr = false; + let is_vlan_attr = false; + let result = {}; + + for (let line = swc.read('line'); length(line); line = swc.read('line')) { + if (match(line, /^\s+--vlan/)) { + is_vlan_attr = true; + } + else if (match(line, /^\s+--port/)) { + is_vlan_attr = false; + is_port_attr = true; + } + else if (match(line, /cpu @/)) { + result.switch_title = match(line, /^switch[0-9]+: \w+\((.+)\)/)?.[1]; + result.num_vlans = match(line, /vlans: ([0-9]+)/)?.[1] ?? 16; + result.min_vid = 1; + } + else if (match(line, /: (pvid|tag|vid)/)) { + if (is_vlan_attr) + result.vid_option = match(line, /: (\w+)/)?.[1]; + } + else if (match(line, /: enable_vlan4k/)) { + result.vlan4k_option = 'enable_vlan4k'; + } + else if (match(line, /: enable_vlan/)) { + result.vlan_option = 'enable_vlan'; + } + else if (match(line, /: enable_learning/)) { + result.learning_option = 'enable_learning'; + } + else if (match(line, /: enable_mirror_rx/)) { + result.mirror_option = 'enable_mirror_rx'; + } + else if (match(line, /: max_length/)) { + result.jumbo_option = 'max_length'; + } + } + + swc.close(); + + if (!length(result)) + return { error: 'No such switch' }; + + return result; + } + else { + return { error: error() }; + } + } + }, + + getSwconfigPortState: { + args: { switch: 'switch0' }, + call: function(request) { + const swc = popen(`swconfig dev ${shellquote(request.args.switch)} show 2>/dev/null`); + + if (swc) { + let ports = [], port; + + for (let line = swc.read('line'); length(line); line = swc.read('line')) { + if (match(line, /^VLAN [0-9]+:/) && length(ports)) + break; + + let pnum = match(line, /^Port ([0-9]+):/)?.[1]; + + if (pnum) { + port = { + port: +pnum, + duplex: false, + speed: 0, + link: false, + auto: false, + rxflow: false, + txflow: false + }; + + push(ports, port); + } + + if (port) { + let m; + + if (match(line, /full[ -]duplex/)) + port.duplex = true; + + if ((m = match(line, / speed:([0-9]+)/)) != null) + port.speed = +m[1]; + + if ((m = match(line, /([0-9]+) Mbps/)) != null && !port.speed) + port.speed = +m[1]; + + if ((m = match(line, /link: ([0-9]+)/)) != null && !port.speed) + port.speed = +m[1]; + + if (match(line, /(link|status): ?up/)) + port.link = true; + + if (match(line, /auto-negotiate|link:.*auto/)) + port.auto = true; + + if (match(line, /link:.*rxflow/)) + port.rxflow = true; + + if (match(line, /link:.*txflow/)) + port.txflow = true; + } + } + + swc.close(); + + if (!length(ports)) + return { error: 'No such switch' }; + + return { result: ports }; + } + else { + return { error: error() }; + } + } + }, + + setPassword: { + args: { username: 'root', password: 'password' }, + call: function(request) { + const u = shellquote(request.args.username); + const p = shellquote(request.args.password); + + return { + result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0 + }; + } + }, + + getBlockDevices: { + call: function() { + const block = popen('/sbin/block info 2>/dev/null'); + + if (block) { + let result = {}; + + for (let line = block.read('line'); length(line); line = block.read('line')) { + let dev = match(line, /^\/dev\/([^:]+):/)?.[1]; + + if (dev) { + let e = result[dev] = { + dev: `/dev/${dev}`, + size: +readfile(`/sys/class/block/${dev}/size`) * 512 + }; + + for (m in match(line, / (\w+)="([^"]+)"/g)) + e[lc(m[1])] = m[2]; + } + } + + block.close(); + + return result; + } + else { + return { error: 'Unable to execute block utility' }; + } + } + }, + + setBlockDetect: { + call: function() { + return { result: system('/sbin/block detect > /etc/config/fstab') == 0 }; + } + }, + + getMountPoints: { + call: function() { + const fd = open('/proc/mounts', 'r'); + + if (fd) { + let result = []; + + for (let line = fd.read('line'); length(line); line = fd.read('line')) { + const m = split(line, ' '); + const device = replace(m[0], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8))); + const mount = replace(m[1], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8))); + const stat = statvfs(mount); + + if (stat?.blocks > 0) { + push(result, { + device, mount, + size: stat.bsize * stat.blocks, + avail: stat.bsize * stat.bavail, + free: stat.bsize * stat.bfree + }); + } + } + + fd.close(); + + return { result }; + } + else { + return { error: error() }; + } + } + }, + getRealtimeStats: { + args: { mode: 'interface', device: 'eth0' }, + call: function(request) { + let flags; + + if (request.args.mode == 'interface') + flags = `-i ${shellquote(request.args.device)}`; + else if (request.args.mode == 'wireless') + flags = `-r ${shellquote(request.args.device)}`; + else if (request.args.mode == 'conntrack') + flags = '-c'; + else if (request.args.mode == 'load') + flags = '-l'; + else + return { error: 'Invalid mode' }; + + const fd = popen(`luci-bwc ${flags}`, 'r'); + + if (fd) { + let result; + + try { + result = { result: json(`[${fd.read('all')}]`) }; + } + catch (err) { + result = { error: err }; + } + + return result; + } + else { + return { error: error() }; + } + } + }, + + getConntrackList: { + call: function() { + return { result: conntrack_list() }; + } + }, + + getProcessList: { + call: function() { + return { result: process_list() }; + } + } +}; + +return { luci: methods }; diff --git a/modules/luci-base/src/Makefile b/modules/luci-base/src/Makefile index 2a425d5ab7..896aeb0a38 100644 --- a/modules/luci-base/src/Makefile +++ b/modules/luci-base/src/Makefile @@ -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 diff --git a/modules/luci-base/src/template_lmo.c b/modules/luci-base/src/lib/lmo.c index 8634bc4bf3..da521bc98b 100644 --- a/modules/luci-base/src/template_lmo.c +++ b/modules/luci-base/src/lib/lmo.c @@ -16,7 +16,7 @@ * limitations under the License. */ -#include "template_lmo.h" +#include "lmo.h" #include "plural_formula.h" /* @@ -24,9 +24,9 @@ * Copyright (C) 2004-2008 by Paul Hsieh */ -uint32_t sfh_hash(const char *data, int len) +uint32_t sfh_hash(const char *data, size_t len, uint32_t init) { - uint32_t hash = len, tmp; + uint32_t hash = init, tmp; int rem; if (len <= 0 || data == NULL) return 0; @@ -137,7 +137,7 @@ uint32_t lmo_canon_hash(const char *str, int len, ptr += snprintf(ptr, 3, "\2%d", plural); } - return sfh_hash(res, ptr - res); + return sfh_hash(res, ptr - res, ptr - res); } lmo_archive_t * lmo_open(const char *file) @@ -550,7 +550,6 @@ int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, uint32_t hash; lmo_entry_t *e; lmo_archive_t *ar; - const char *plural_formula; if (!skey || !pkey || !_lmo_active_catalog) return -2; diff --git a/modules/luci-base/src/template_lmo.h b/modules/luci-base/src/lib/lmo.h index d6cba7bf49..744209f62c 100644 --- a/modules/luci-base/src/template_lmo.h +++ b/modules/luci-base/src/lib/lmo.h @@ -41,6 +41,10 @@ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif +#ifndef __hidden +#define __hidden __attribute__((visibility("hidden"))) +#endif + struct lmo_entry { uint32_t key_id; @@ -75,30 +79,30 @@ typedef struct lmo_catalog lmo_catalog_t; typedef void (*lmo_iterate_cb_t)(uint32_t, const char *, int, void *); -uint32_t sfh_hash(const char *data, int len); -uint32_t lmo_canon_hash(const char *data, int len, - const char *ctx, int ctxlen, int plural); +__hidden uint32_t sfh_hash(const char *data, size_t len, uint32_t init); +__hidden uint32_t lmo_canon_hash(const char *data, int len, + const char *ctx, int ctxlen, int plural); -lmo_archive_t * lmo_open(const char *file); -void lmo_close(lmo_archive_t *ar); +__hidden lmo_archive_t * lmo_open(const char *file); +__hidden void lmo_close(lmo_archive_t *ar); -extern lmo_catalog_t *_lmo_catalogs; -extern lmo_catalog_t *_lmo_active_catalog; +__hidden extern lmo_catalog_t *_lmo_catalogs; +__hidden extern lmo_catalog_t *_lmo_active_catalog; -int lmo_load_catalog(const char *lang, const char *dir); -int lmo_change_catalog(const char *lang); -int lmo_translate(const char *key, int keylen, char **out, int *outlen); -int lmo_translate_ctxt(const char *key, int keylen, - const char *ctx, int ctxlen, char **out, int *outlen); -int lmo_translate_plural(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - char **out, int *outlen); -int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - const char *ctx, int ctxlen, +__hidden int lmo_load_catalog(const char *lang, const char *dir); +__hidden int lmo_change_catalog(const char *lang); +__hidden int lmo_translate(const char *key, int keylen, char **out, int *outlen); +__hidden int lmo_translate_ctxt(const char *key, int keylen, + const char *ctx, int ctxlen, char **out, int *outlen); +__hidden int lmo_translate_plural(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + char **out, int *outlen); +__hidden int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + const char *ctx, int ctxlen, char **out, int *outlen); -void lmo_iterate(lmo_iterate_cb_t cb, void *priv); -void lmo_close_catalog(const char *lang); +__hidden void lmo_iterate(lmo_iterate_cb_t cb, void *priv); +__hidden void lmo_close_catalog(const char *lang); #endif diff --git a/modules/luci-base/src/lib/luci.c b/modules/luci-base/src/lib/luci.c new file mode 100644 index 0000000000..e6860e727d --- /dev/null +++ b/modules/luci-base/src/lib/luci.c @@ -0,0 +1,383 @@ +/* + * LuCI low level routines - ucode binding + * + * Copyright (C) 2009-2022 Jo-Philipp Wich <jo@mein.io> + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lmo.h" + +#include <pwd.h> +#include <crypt.h> +#include <shadow.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/sysinfo.h> +#include <sys/statvfs.h> + +#include <ucode/module.h> + +/* translation catalog functions */ + +static uc_value_t * +uc_luci_load_catalog(uc_vm_t *vm, size_t nargs) { + uc_value_t *lang = uc_fn_arg(0); + uc_value_t *dir = uc_fn_arg(1); + + if (lang && ucv_type(lang) != UC_STRING) + return NULL; + + if (dir && ucv_type(dir) != UC_STRING) + return NULL; + + return ucv_boolean_new(lmo_load_catalog( + lang ? ucv_string_get(lang) : "en", + ucv_string_get(dir)) == 0); +} + +static uc_value_t * +uc_luci_close_catalog(uc_vm_t *vm, size_t nargs) { + uc_value_t *lang = uc_fn_arg(0); + + if (lang && ucv_type(lang) != UC_STRING) + return NULL; + + lmo_close_catalog(lang ? ucv_string_get(lang) : "en"); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_luci_change_catalog(uc_vm_t *vm, size_t nargs) { + uc_value_t *lang = uc_fn_arg(0); + + if (lang && ucv_type(lang) != UC_STRING) + return NULL; + + return ucv_boolean_new(lmo_change_catalog( + lang ? ucv_string_get(lang) : "en") == 0); +} + +static void +uc_luci_get_translations_cb(uint32_t key, const char *val, int len, void *priv) { + uc_vm_t *vm = priv; + + uc_vm_stack_push(vm, ucv_get(uc_vm_stack_peek(vm, 0))); + uc_vm_stack_push(vm, ucv_uint64_new(key)); + uc_vm_stack_push(vm, ucv_string_new_length(val, (size_t)len)); + + if (uc_vm_call(vm, false, 2) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(vm)); +} + +static uc_value_t * +uc_luci_get_translations(uc_vm_t *vm, size_t nargs) { + lmo_iterate(uc_luci_get_translations_cb, vm); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_luci_translate(uc_vm_t *vm, size_t nargs) { + uc_value_t *key = uc_fn_arg(0); + uc_value_t *ctx = uc_fn_arg(1); + int trlen; + char *tr; + + if (ucv_type(key) != UC_STRING) + return NULL; + + if (ctx && ucv_type(ctx) != UC_STRING) + return NULL; + + if (lmo_translate_ctxt(ucv_string_get(key), ucv_string_length(key), + ucv_string_get(ctx), ucv_string_length(ctx), + &tr, &trlen) != 0) + return NULL; + + return ucv_string_new_length(tr, (size_t)trlen); +} + +static uc_value_t * +uc_luci_ntranslate(uc_vm_t *vm, size_t nargs) { + uc_value_t *cnt = uc_fn_arg(0); + uc_value_t *skey = uc_fn_arg(1); + uc_value_t *pkey = uc_fn_arg(2); + uc_value_t *ctx = uc_fn_arg(3); + int trlen; + char *tr; + + if (ucv_type(skey) != UC_STRING || ucv_type(pkey) != UC_STRING) + return NULL; + + if (ctx && ucv_type(ctx) != UC_STRING) + return NULL; + + if (lmo_translate_plural_ctxt(ucv_int64_get(cnt), + ucv_string_get(skey), ucv_string_length(skey), + ucv_string_get(pkey), ucv_string_length(pkey), + ucv_string_get(ctx), ucv_string_length(ctx), + &tr, &trlen) != 0) + return NULL; + + return ucv_string_new_length(tr, (size_t)trlen); +} + +static uc_value_t * +uc_luci_hash(uc_vm_t *vm, size_t nargs) { + uc_value_t *key = uc_fn_arg(0); + uc_value_t *init = uc_fn_arg(1); + + if (ucv_type(key) != UC_STRING) + return NULL; + + if (init && ucv_type(init) != UC_INTEGER) + return NULL; + + return ucv_uint64_new(sfh_hash(ucv_string_get(key), ucv_string_length(key), + init ? ucv_uint64_get(init) : ucv_string_length(key))); +} + + +/* user functions */ + +static uc_value_t * +uc_luci_getspnam(uc_vm_t *vm, size_t nargs) { + uc_value_t *name = uc_fn_arg(0), *rv; + struct spwd *s; + + if (ucv_type(name) != UC_STRING) + return NULL; + + s = getspnam(ucv_string_get(name)); + + if (!s) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "namp", ucv_string_new(s->sp_namp)); + ucv_object_add(rv, "pwdp", ucv_string_new(s->sp_pwdp)); + ucv_object_add(rv, "lstchg", ucv_int64_new(s->sp_lstchg)); + ucv_object_add(rv, "min", ucv_int64_new(s->sp_min)); + ucv_object_add(rv, "max", ucv_int64_new(s->sp_max)); + ucv_object_add(rv, "warn", ucv_int64_new(s->sp_warn)); + ucv_object_add(rv, "inact", ucv_int64_new(s->sp_inact)); + ucv_object_add(rv, "expire", ucv_int64_new(s->sp_expire)); + + return rv; +} + +static uc_value_t * +uc_luci_getpwnam(uc_vm_t *vm, size_t nargs) { + uc_value_t *name = uc_fn_arg(0), *rv; + struct passwd *p; + + if (ucv_type(name) != UC_STRING) + return NULL; + + p = getpwnam(ucv_string_get(name)); + + if (!p) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "name", ucv_string_new(p->pw_name)); + ucv_object_add(rv, "passwd", ucv_string_new(p->pw_passwd)); + ucv_object_add(rv, "uid", ucv_int64_new(p->pw_uid)); + ucv_object_add(rv, "gid", ucv_int64_new(p->pw_gid)); + ucv_object_add(rv, "gecos", ucv_string_new(p->pw_gecos)); + ucv_object_add(rv, "dir", ucv_string_new(p->pw_dir)); + ucv_object_add(rv, "shell", ucv_string_new(p->pw_shell)); + + return rv; +} + +static uc_value_t * +uc_luci_crypt(uc_vm_t *vm, size_t nargs) { + uc_value_t *phrase = uc_fn_arg(0); + uc_value_t *setting = uc_fn_arg(1); + char *hash; + + if (ucv_type(phrase) != UC_STRING || ucv_type(setting) != UC_STRING) + return NULL; + + errno = 0; + hash = crypt(ucv_string_get(phrase), ucv_string_get(setting)); + + if (hash == NULL || errno != 0) + return NULL; + + return ucv_string_new(hash); +} + +static uc_value_t * +uc_luci_getuid(uc_vm_t *vm, size_t nargs) { + return ucv_int64_new(getuid()); +} + +static uc_value_t * +uc_luci_getgid(uc_vm_t *vm, size_t nargs) { + return ucv_int64_new(getgid()); +} + +static uc_value_t * +uc_luci_setuid(uc_vm_t *vm, size_t nargs) { + uc_value_t *uid = uc_fn_arg(0); + + if (ucv_type(uid) != UC_INTEGER) + return NULL; + + return ucv_boolean_new(setuid(ucv_int64_get(uid)) == 0); +} + +static uc_value_t * +uc_luci_setgid(uc_vm_t *vm, size_t nargs) { + uc_value_t *gid = uc_fn_arg(0); + + if (ucv_type(gid) != UC_INTEGER) + return NULL; + + return ucv_boolean_new(setgid(ucv_int64_get(gid)) == 0); +} + + +/* misc functions */ + +static uc_value_t * +uc_luci_kill(uc_vm_t *vm, size_t nargs) { + uc_value_t *pid = uc_fn_arg(0); + uc_value_t *sig = uc_fn_arg(1); + + if (ucv_type(pid) != UC_INTEGER || ucv_type(sig) != UC_INTEGER) + return NULL; + + return ucv_boolean_new(kill(ucv_int64_get(pid), ucv_int64_get(sig)) == 0); +} + +static uc_value_t * +uc_luci_uname(uc_vm_t *vm, size_t nargs) { + struct utsname u; + uc_value_t *rv; + + if (uname(&u) == -1) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "sysname", ucv_string_new(u.sysname)); + ucv_object_add(rv, "nodename", ucv_string_new(u.nodename)); + ucv_object_add(rv, "release", ucv_string_new(u.release)); + ucv_object_add(rv, "version", ucv_string_new(u.version)); + ucv_object_add(rv, "machine", ucv_string_new(u.machine)); + + return rv; +} + +static uc_value_t * +uc_luci_sysinfo(uc_vm_t *vm, size_t nargs) { + uc_value_t *rv, *loads; + struct sysinfo i; + + if (sysinfo(&i) == -1) + return NULL; + + rv = ucv_object_new(vm); + loads = ucv_array_new_length(vm, 3); + + ucv_array_push(loads, ucv_uint64_new(i.loads[0])); + ucv_array_push(loads, ucv_uint64_new(i.loads[1])); + ucv_array_push(loads, ucv_uint64_new(i.loads[2])); + + ucv_object_add(rv, "uptime", ucv_int64_new(i.uptime)); + ucv_object_add(rv, "loads", loads); + ucv_object_add(rv, "totalram", ucv_uint64_new(i.totalram)); + ucv_object_add(rv, "freeram", ucv_uint64_new(i.freeram)); + ucv_object_add(rv, "sharedram", ucv_uint64_new(i.sharedram)); + ucv_object_add(rv, "bufferram", ucv_uint64_new(i.bufferram)); + ucv_object_add(rv, "totalswap", ucv_uint64_new(i.totalswap)); + ucv_object_add(rv, "freeswap", ucv_uint64_new(i.freeswap)); + ucv_object_add(rv, "procs", ucv_uint64_new(i.procs)); + ucv_object_add(rv, "totalhigh", ucv_uint64_new(i.totalhigh)); + ucv_object_add(rv, "freehigh", ucv_uint64_new(i.freehigh)); + ucv_object_add(rv, "mem_unit", ucv_uint64_new(i.mem_unit)); + + return rv; +} + +static uc_value_t * +uc_luci_statvfs(uc_vm_t *vm, size_t nargs) { + uc_value_t *path = uc_fn_arg(0), *rv; + struct statvfs s; + + if (ucv_type(path) != UC_STRING) + return NULL; + + if (statvfs(ucv_string_get(path), &s) == -1) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "bsize", ucv_uint64_new(s.f_bsize)); + ucv_object_add(rv, "frsize", ucv_uint64_new(s.f_frsize)); + + ucv_object_add(rv, "blocks", ucv_uint64_new(s.f_blocks)); + ucv_object_add(rv, "bfree", ucv_uint64_new(s.f_bfree)); + ucv_object_add(rv, "bavail", ucv_uint64_new(s.f_bavail)); + + ucv_object_add(rv, "files", ucv_uint64_new(s.f_files)); + ucv_object_add(rv, "ffree", ucv_uint64_new(s.f_ffree)); + ucv_object_add(rv, "favail", ucv_uint64_new(s.f_favail)); + + ucv_object_add(rv, "fsid", ucv_uint64_new(s.f_fsid)); + ucv_object_add(rv, "flag", ucv_uint64_new(s.f_flag)); + ucv_object_add(rv, "namemax", ucv_uint64_new(s.f_namemax)); + + return rv; +} + + +static const uc_function_list_t luci_fns[] = { + { "load_catalog", uc_luci_load_catalog }, + { "close_catalog", uc_luci_close_catalog }, + { "change_catalog", uc_luci_change_catalog }, + { "get_translations", uc_luci_get_translations }, + { "translate", uc_luci_translate }, + { "ntranslate", uc_luci_ntranslate }, + { "hash", uc_luci_hash }, + + { "getspnam", uc_luci_getspnam }, + { "getpwnam", uc_luci_getpwnam }, + { "crypt", uc_luci_crypt }, + { "getuid", uc_luci_getuid }, + { "setuid", uc_luci_setuid }, + { "getgid", uc_luci_getgid }, + { "setgid", uc_luci_setgid }, + + { "kill", uc_luci_kill }, + { "uname", uc_luci_uname }, + { "sysinfo", uc_luci_sysinfo }, + { "statvfs", uc_luci_statvfs }, +}; + + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, luci_fns); +} diff --git a/modules/luci-base/src/plural_formula.y b/modules/luci-base/src/lib/plural_formula.y index 1623f8b282..1623f8b282 100644 --- a/modules/luci-base/src/plural_formula.y +++ b/modules/luci-base/src/lib/plural_formula.y diff --git a/modules/luci-base/src/mkversion.sh b/modules/luci-base/src/mkversion.sh deleted file mode 100755 index e2d02c1c74..0000000000 --- a/modules/luci-base/src/mkversion.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -cat <<EOF > $1 -local pcall, dofile, _G = pcall, dofile, _G - -module "luci.version" - -if pcall(dofile, "/etc/openwrt_release") and _G.DISTRIB_DESCRIPTION then - distname = "" - distversion = _G.DISTRIB_DESCRIPTION - if _G.DISTRIB_REVISION then - distrevision = _G.DISTRIB_REVISION - if not distversion:find(distrevision,1,true) then - distversion = distversion .. " " .. distrevision - end - end -else - distname = "OpenWrt" - distversion = "Development Snapshot" -end - -luciname = "${3:-LuCI}" -luciversion = "${2:-Git}" -EOF diff --git a/modules/luci-base/src/po2lmo.c b/modules/luci-base/src/po2lmo.c index 5f398c266e..0a04e9ba17 100644 --- a/modules/luci-base/src/po2lmo.c +++ b/modules/luci-base/src/po2lmo.c @@ -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++; diff --git a/modules/luci-base/src/template_lualib.c b/modules/luci-base/src/template_lualib.c deleted file mode 100644 index 4efd9f1de6..0000000000 --- a/modules/luci-base/src/template_lualib.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * LuCI Template - Lua binding - * - * Copyright (C) 2009 Jo-Philipp Wich <jow@openwrt.org> - * - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_lualib.h" - -static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname) -{ - int lua_status, rv; - - if (!parser) - { - lua_pushnil(L); - lua_pushinteger(L, errno); - lua_pushstring(L, strerror(errno)); - return 3; - } - - lua_status = lua_load(L, template_reader, parser, chunkname); - - if (lua_status == 0) - rv = 1; - else - rv = template_error(L, parser); - - template_close(parser); - - return rv; -} - -int template_L_parse(lua_State *L) -{ - const char *file = luaL_checkstring(L, 1); - struct template_parser *parser = template_open(file); - - return template_L_do_parse(L, parser, file); -} - -int template_L_parse_string(lua_State *L) -{ - size_t len; - const char *str = luaL_checklstring(L, 1, &len); - struct template_parser *parser = template_string(str, len); - - return template_L_do_parse(L, parser, "[string]"); -} - -int template_L_utf8(lua_State *L) -{ - size_t len = 0; - const char *str = luaL_checklstring(L, 1, &len); - char *res = utf8(str, len); - - if (res != NULL) - { - lua_pushstring(L, res); - free(res); - - return 1; - } - - return 0; -} - -int template_L_pcdata(lua_State *L) -{ - size_t len = 0; - const char *str = luaL_checklstring(L, 1, &len); - char *res = pcdata(str, len); - - if (res != NULL) - { - lua_pushstring(L, res); - free(res); - - return 1; - } - - return 0; -} - -int template_L_striptags(lua_State *L) -{ - size_t len = 0; - const char *str = luaL_checklstring(L, 1, &len); - char *res = striptags(str, len); - - if (res != NULL) - { - lua_pushstring(L, res); - free(res); - - return 1; - } - - return 0; -} - -static int template_L_load_catalog(lua_State *L) { - const char *lang = luaL_optstring(L, 1, "en"); - const char *dir = luaL_optstring(L, 2, NULL); - lua_pushboolean(L, !lmo_load_catalog(lang, dir)); - return 1; -} - -static int template_L_close_catalog(lua_State *L) { - const char *lang = luaL_optstring(L, 1, "en"); - lmo_close_catalog(lang); - return 0; -} - -static int template_L_change_catalog(lua_State *L) { - const char *lang = luaL_optstring(L, 1, "en"); - lua_pushboolean(L, !lmo_change_catalog(lang)); - return 1; -} - -static void template_L_get_translations_cb(uint32_t key, const char *val, int len, void *priv) { - lua_State *L = priv; - char hex[9]; - - luaL_checktype(L, 1, LUA_TFUNCTION); - snprintf(hex, sizeof(hex), "%08x", key); - - lua_pushvalue(L, 1); - lua_pushstring(L, hex); - lua_pushlstring(L, val, len); - lua_call(L, 2, 0); -} - -static int template_L_get_translations(lua_State *L) { - lmo_iterate(template_L_get_translations_cb, L); - return 0; -} - -static int template_L_translate(lua_State *L) { - size_t len, ctxlen = 0; - char *tr; - int trlen; - const char *key = luaL_checklstring(L, 1, &len); - const char *ctx = luaL_optlstring(L, 2, NULL, &ctxlen); - - switch (lmo_translate_ctxt(key, len, ctx, ctxlen, &tr, &trlen)) - { - case 0: - lua_pushlstring(L, tr, trlen); - return 1; - - case -1: - return 0; - } - - lua_pushnil(L); - lua_pushstring(L, "no catalog loaded"); - return 2; -} - -static int template_L_ntranslate(lua_State *L) { - size_t slen, plen, ctxlen = 0; - char *tr; - int trlen; - int n = luaL_checkinteger(L, 1); - const char *skey = luaL_checklstring(L, 2, &slen); - const char *pkey = luaL_checklstring(L, 3, &plen); - const char *ctx = luaL_optlstring(L, 4, NULL, &ctxlen); - - switch (lmo_translate_plural_ctxt(n, skey, slen, pkey, plen, ctx, ctxlen, &tr, &trlen)) - { - case 0: - lua_pushlstring(L, tr, trlen); - return 1; - - case -1: - return 0; - } - - lua_pushnil(L); - lua_pushstring(L, "no catalog loaded"); - return 2; -} - -static int template_L_hash(lua_State *L) { - size_t len; - const char *key = luaL_checklstring(L, 1, &len); - lua_pushinteger(L, sfh_hash(key, len)); - return 1; -} - - -/* module table */ -static const luaL_reg R[] = { - { "parse", template_L_parse }, - { "parse_string", template_L_parse_string }, - { "utf8", template_L_utf8 }, - { "pcdata", template_L_pcdata }, - { "striptags", template_L_striptags }, - { "load_catalog", template_L_load_catalog }, - { "close_catalog", template_L_close_catalog }, - { "change_catalog", template_L_change_catalog }, - { "get_translations", template_L_get_translations }, - { "translate", template_L_translate }, - { "ntranslate", template_L_ntranslate }, - { "hash", template_L_hash }, - { NULL, NULL } -}; - -LUALIB_API int luaopen_luci_template_parser(lua_State *L) { - luaL_register(L, TEMPLATE_LUALIB_META, R); - return 1; -} diff --git a/modules/luci-base/src/template_lualib.h b/modules/luci-base/src/template_lualib.h deleted file mode 100644 index ff7746d158..0000000000 --- a/modules/luci-base/src/template_lualib.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * LuCI Template - Lua library header - * - * Copyright (C) 2009 Jo-Philipp Wich <jow@openwrt.org> - * - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_LUALIB_H_ -#define _TEMPLATE_LUALIB_H_ - -#include "template_parser.h" -#include "template_utils.h" -#include "template_lmo.h" - -#define TEMPLATE_LUALIB_META "template.parser" - -LUALIB_API int luaopen_luci_template_parser(lua_State *L); - -#endif diff --git a/modules/luci-base/src/template_parser.c b/modules/luci-base/src/template_parser.c deleted file mode 100644 index 0ef08c63d2..0000000000 --- a/modules/luci-base/src/template_parser.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * LuCI Template - Parser implementation - * - * Copyright (C) 2009-2012 Jo-Philipp Wich <jow@openwrt.org> - * - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_parser.h" -#include "template_utils.h" -#include "template_lmo.h" - - -/* leading and trailing code for different types */ -const char *gen_code[9][2] = { - { NULL, NULL }, - { "write(\"", "\")" }, - { NULL, NULL }, - { "write(tostring(", " or \"\"))" }, - { "include(\"", "\")" }, - { "write(\"", "\")" }, - { "write(\"", "\")" }, - { NULL, " " }, - { NULL, NULL }, -}; - -/* Simple strstr() like function that takes len arguments for both haystack and needle. */ -static char *strfind(char *haystack, int hslen, const char *needle, int ndlen) -{ - int match = 0; - int i, j; - - for( i = 0; i < hslen; i++ ) - { - if( haystack[i] == needle[0] ) - { - match = ((ndlen == 1) || ((i + ndlen) <= hslen)); - - for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ ) - { - if( haystack[i+j] != needle[j] ) - { - match = 0; - break; - } - } - - if( match ) - return &haystack[i]; - } - } - - return NULL; -} - -struct template_parser * template_open(const char *file) -{ - struct stat s; - struct template_parser *parser; - - if (!(parser = malloc(sizeof(*parser)))) - goto err; - - memset(parser, 0, sizeof(*parser)); - parser->fd = -1; - parser->file = file; - - if (stat(file, &s)) - goto err; - - if ((parser->fd = open(file, O_RDONLY)) < 0) - goto err; - - parser->size = s.st_size; - parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE, - parser->fd, 0); - - if (parser->data != MAP_FAILED) - { - parser->off = parser->data; - parser->cur_chunk.type = T_TYPE_INIT; - parser->cur_chunk.s = parser->data; - parser->cur_chunk.e = parser->data; - - return parser; - } - -err: - template_close(parser); - return NULL; -} - -struct template_parser * template_string(const char *str, uint32_t len) -{ - struct template_parser *parser; - - if (!str) { - errno = EINVAL; - goto err; - } - - if (!(parser = malloc(sizeof(*parser)))) - goto err; - - memset(parser, 0, sizeof(*parser)); - parser->fd = -1; - - parser->size = len; - parser->data = (char*)str; - - parser->off = parser->data; - parser->cur_chunk.type = T_TYPE_INIT; - parser->cur_chunk.s = parser->data; - parser->cur_chunk.e = parser->data; - - return parser; - -err: - template_close(parser); - return NULL; -} - -void template_close(struct template_parser *parser) -{ - if (!parser) - return; - - if (parser->gc != NULL) - free(parser->gc); - - /* if file is not set, we were parsing a string */ - if (parser->file) { - if ((parser->data != NULL) && (parser->data != MAP_FAILED)) - munmap(parser->data, parser->size); - - if (parser->fd >= 0) - close(parser->fd); - } - - free(parser); -} - -void template_text(struct template_parser *parser, const char *e) -{ - const char *s = parser->off; - - if (s < (parser->data + parser->size)) - { - if (parser->strip_after) - { - while ((s <= e) && isspace(*s)) - s++; - } - - parser->cur_chunk.type = T_TYPE_TEXT; - } - else - { - parser->cur_chunk.type = T_TYPE_EOF; - } - - parser->cur_chunk.line = parser->line; - parser->cur_chunk.s = s; - parser->cur_chunk.e = e; -} - -void template_code(struct template_parser *parser, const char *e) -{ - const char *s = parser->off; - - parser->strip_before = 0; - parser->strip_after = 0; - - if (*s == '-') - { - parser->strip_before = 1; - for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++); - } - - if (*(e-1) == '-') - { - parser->strip_after = 1; - for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--); - } - - switch (*s) - { - /* comment */ - case '#': - s++; - parser->cur_chunk.type = T_TYPE_COMMENT; - break; - - /* include */ - case '+': - s++; - parser->cur_chunk.type = T_TYPE_INCLUDE; - break; - - /* translate */ - case ':': - s++; - parser->cur_chunk.type = T_TYPE_I18N; - break; - - /* translate raw */ - case '_': - s++; - parser->cur_chunk.type = T_TYPE_I18N_RAW; - break; - - /* expr */ - case '=': - s++; - parser->cur_chunk.type = T_TYPE_EXPR; - break; - - /* code */ - default: - parser->cur_chunk.type = T_TYPE_CODE; - break; - } - - parser->cur_chunk.line = parser->line; - parser->cur_chunk.s = s; - parser->cur_chunk.e = e; -} - -static const char * -template_format_chunk(struct template_parser *parser, size_t *sz) -{ - const char *s, *p; - const char *head, *tail; - struct template_chunk *c = &parser->prv_chunk; - struct template_buffer *buf; - - *sz = 0; - s = parser->gc = NULL; - - if (parser->strip_before && c->type == T_TYPE_TEXT) - { - while ((c->e > c->s) && isspace(*(c->e - 1))) - c->e--; - } - - /* empty chunk */ - if (c->s == c->e) - { - if (c->type == T_TYPE_EOF) - { - *sz = 0; - s = NULL; - } - else - { - *sz = 1; - s = " "; - } - } - - /* format chunk */ - else if ((buf = buf_init(c->e - c->s)) != NULL) - { - if ((head = gen_code[c->type][0]) != NULL) - buf_append(buf, head, strlen(head)); - - switch (c->type) - { - case T_TYPE_TEXT: - luastr_escape(buf, c->s, c->e - c->s, 0); - break; - - case T_TYPE_EXPR: - buf_append(buf, c->s, c->e - c->s); - for (p = c->s; p < c->e; p++) - parser->line += (*p == '\n'); - break; - - case T_TYPE_INCLUDE: - luastr_escape(buf, c->s, c->e - c->s, 0); - break; - - case T_TYPE_I18N: - luastr_translate(buf, c->s, c->e - c->s, 1); - break; - - case T_TYPE_I18N_RAW: - luastr_translate(buf, c->s, c->e - c->s, 0); - break; - - case T_TYPE_CODE: - buf_append(buf, c->s, c->e - c->s); - for (p = c->s; p < c->e; p++) - parser->line += (*p == '\n'); - break; - } - - if ((tail = gen_code[c->type][1]) != NULL) - buf_append(buf, tail, strlen(tail)); - - *sz = buf_length(buf); - s = parser->gc = buf_destroy(buf); - - if (!*sz) - { - *sz = 1; - s = " "; - } - } - - return s; -} - -const char *template_reader(lua_State *L, void *ud, size_t *sz) -{ - struct template_parser *parser = ud; - int rem = parser->size - (parser->off - parser->data); - char *tag; - - parser->prv_chunk = parser->cur_chunk; - - /* free previous string */ - if (parser->gc) - { - free(parser->gc); - parser->gc = NULL; - } - - /* before tag */ - if (!parser->in_expr) - { - if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL) - { - template_text(parser, tag); - parser->off = tag + 2; - parser->in_expr = 1; - } - else - { - template_text(parser, parser->data + parser->size); - parser->off = parser->data + parser->size; - } - } - - /* inside tag */ - else - { - if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL) - { - template_code(parser, tag); - parser->off = tag + 2; - parser->in_expr = 0; - } - else - { - /* unexpected EOF */ - template_code(parser, parser->data + parser->size); - - *sz = 1; - return "\033"; - } - } - - return template_format_chunk(parser, sz); -} - -int template_error(lua_State *L, struct template_parser *parser) -{ - const char *err = luaL_checkstring(L, -1); - const char *off = parser->prv_chunk.s; - const char *ptr; - char msg[1024]; - int line = 0; - int chunkline = 0; - - if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL) - { - chunkline = atoi(ptr + 2) - parser->prv_chunk.line; - - while (*ptr) - { - if (*ptr++ == ' ') - { - err = ptr; - break; - } - } - } - - if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL) - { - off = parser->data + parser->size; - err = "'%>' expected before end of file"; - chunkline = 0; - } - - for (ptr = parser->data; ptr < off; ptr++) - if (*ptr == '\n') - line++; - - snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s", - parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)"); - - lua_pushnil(L); - lua_pushinteger(L, line + chunkline); - lua_pushstring(L, msg); - - return 3; -} diff --git a/modules/luci-base/src/template_parser.h b/modules/luci-base/src/template_parser.h deleted file mode 100644 index 2415e87079..0000000000 --- a/modules/luci-base/src/template_parser.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * LuCI Template - Parser header - * - * Copyright (C) 2009 Jo-Philipp Wich <jow@openwrt.org> - * - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_PARSER_H_ -#define _TEMPLATE_PARSER_H_ - -#include <stdlib.h> -#include <stdio.h> -#include <stdint.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/mman.h> -#include <string.h> -#include <ctype.h> -#include <errno.h> - -#include <lua.h> -#include <lualib.h> -#include <lauxlib.h> - - -/* code types */ -#define T_TYPE_INIT 0 -#define T_TYPE_TEXT 1 -#define T_TYPE_COMMENT 2 -#define T_TYPE_EXPR 3 -#define T_TYPE_INCLUDE 4 -#define T_TYPE_I18N 5 -#define T_TYPE_I18N_RAW 6 -#define T_TYPE_CODE 7 -#define T_TYPE_EOF 8 - - -struct template_chunk { - const char *s; - const char *e; - int type; - int line; -}; - -/* parser state */ -struct template_parser { - int fd; - uint32_t size; - char *data; - char *off; - char *gc; - int line; - int in_expr; - int strip_before; - int strip_after; - struct template_chunk prv_chunk; - struct template_chunk cur_chunk; - const char *file; -}; - -struct template_parser * template_open(const char *file); -struct template_parser * template_string(const char *str, uint32_t len); -void template_close(struct template_parser *parser); - -const char *template_reader(lua_State *L, void *ud, size_t *sz); -int template_error(lua_State *L, struct template_parser *parser); - -#endif diff --git a/modules/luci-base/src/template_utils.c b/modules/luci-base/src/template_utils.c deleted file mode 100644 index 8580405e32..0000000000 --- a/modules/luci-base/src/template_utils.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * LuCI Template - Utility functions - * - * Copyright (C) 2010 Jo-Philipp Wich <jow@openwrt.org> - * - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_utils.h" -#include "template_lmo.h" - -/* initialize a buffer object */ -struct template_buffer * buf_init(int size) -{ - struct template_buffer *buf; - - if (size <= 0) - size = 1024; - - buf = (struct template_buffer *)malloc(sizeof(struct template_buffer)); - - if (buf != NULL) - { - buf->fill = 0; - buf->size = size; - buf->data = malloc(buf->size); - - if (buf->data != NULL) - { - buf->dptr = buf->data; - buf->data[0] = 0; - - return buf; - } - - free(buf); - } - - return NULL; -} - -/* grow buffer */ -int buf_grow(struct template_buffer *buf, int size) -{ - unsigned int off = (buf->dptr - buf->data); - char *data; - - if (size <= 0) - size = 1024; - - data = realloc(buf->data, buf->size + size); - - if (data != NULL) - { - buf->data = data; - buf->dptr = data + off; - buf->size += size; - - return buf->size; - } - - return 0; -} - -/* put one char into buffer object */ -int buf_putchar(struct template_buffer *buf, char c) -{ - if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) ) - return 0; - - *(buf->dptr++) = c; - *(buf->dptr) = 0; - - buf->fill++; - return 1; -} - -/* append data to buffer */ -int buf_append(struct template_buffer *buf, const char *s, int len) -{ - if ((buf->fill + len + 1) >= buf->size) - { - if (!buf_grow(buf, len + 1)) - return 0; - } - - memcpy(buf->dptr, s, len); - buf->fill += len; - buf->dptr += len; - - *(buf->dptr) = 0; - - return len; -} - -/* read buffer length */ -int buf_length(struct template_buffer *buf) -{ - return buf->fill; -} - -/* destroy buffer object and return pointer to data */ -char * buf_destroy(struct template_buffer *buf) -{ - char *data = buf->data; - - free(buf); - return data; -} - - -/* calculate the number of expected continuation chars */ -static inline int mb_num_chars(unsigned char c) -{ - if ((c & 0xE0) == 0xC0) - return 2; - else if ((c & 0xF0) == 0xE0) - return 3; - else if ((c & 0xF8) == 0xF0) - return 4; - else if ((c & 0xFC) == 0xF8) - return 5; - else if ((c & 0xFE) == 0xFC) - return 6; - - return 1; -} - -/* test whether the given byte is a valid continuation char */ -static inline int mb_is_cont(unsigned char c) -{ - return ((c >= 0x80) && (c <= 0xBF)); -} - -/* test whether the byte sequence at the given pointer with the given - * length is the shortest possible representation of the code point */ -static inline int mb_is_shortest(unsigned char *s, int n) -{ - switch (n) - { - case 2: - /* 1100000x (10xxxxxx) */ - return !(((*s >> 1) == 0x60) && - ((*(s+1) >> 6) == 0x02)); - - case 3: - /* 11100000 100xxxxx (10xxxxxx) */ - return !((*s == 0xE0) && - ((*(s+1) >> 5) == 0x04) && - ((*(s+2) >> 6) == 0x02)); - - case 4: - /* 11110000 1000xxxx (10xxxxxx 10xxxxxx) */ - return !((*s == 0xF0) && - ((*(s+1) >> 4) == 0x08) && - ((*(s+2) >> 6) == 0x02) && - ((*(s+3) >> 6) == 0x02)); - - case 5: - /* 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) */ - return !((*s == 0xF8) && - ((*(s+1) >> 3) == 0x10) && - ((*(s+2) >> 6) == 0x02) && - ((*(s+3) >> 6) == 0x02) && - ((*(s+4) >> 6) == 0x02)); - - case 6: - /* 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) */ - return !((*s == 0xF8) && - ((*(s+1) >> 2) == 0x20) && - ((*(s+2) >> 6) == 0x02) && - ((*(s+3) >> 6) == 0x02) && - ((*(s+4) >> 6) == 0x02) && - ((*(s+5) >> 6) == 0x02)); - } - - return 1; -} - -/* test whether the byte sequence at the given pointer with the given - * length is an UTF-16 surrogate */ -static inline int mb_is_surrogate(unsigned char *s, int n) -{ - return ((n == 3) && (*s == 0xED) && (*(s+1) >= 0xA0) && (*(s+1) <= 0xBF)); -} - -/* test whether the byte sequence at the given pointer with the given - * length is an illegal UTF-8 code point */ -static inline int mb_is_illegal(unsigned char *s, int n) -{ - return ((n == 3) && (*s == 0xEF) && (*(s+1) == 0xBF) && - (*(s+2) >= 0xBE) && (*(s+2) <= 0xBF)); -} - - -/* scan given source string, validate UTF-8 sequence and store result - * in given buffer object */ -static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf) -{ - unsigned char *ptr = *s; - unsigned int o = 0, v, n; - - /* ascii byte without null */ - if ((*(ptr+0) >= 0x01) && (*(ptr+0) <= 0x7F)) - { - if (!buf_putchar(buf, *ptr++)) - return 0; - - o = 1; - } - - /* multi byte sequence */ - else if ((n = mb_num_chars(*ptr)) > 1) - { - /* count valid chars */ - for (v = 1; (v <= n) && ((o+v) < l) && mb_is_cont(*(ptr+v)); v++); - - switch (n) - { - case 6: - case 5: - /* five and six byte sequences are always invalid */ - if (!buf_putchar(buf, '?')) - return 0; - - break; - - default: - /* if the number of valid continuation bytes matches the - * expected number and if the sequence is legal, copy - * the bytes to the destination buffer */ - if ((v == n) && mb_is_shortest(ptr, n) && - !mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n)) - { - /* copy sequence */ - if (!buf_append(buf, (char *)ptr, n)) - return 0; - } - - /* the found sequence is illegal, skip it */ - else - { - /* invalid sequence */ - if (!buf_putchar(buf, '?')) - return 0; - } - - break; - } - - /* advance beyond the last found valid continuation char */ - o = v; - ptr += v; - } - - /* invalid byte (0x00) */ - else - { - if (!buf_putchar(buf, '?')) /* or 0xEF, 0xBF, 0xBD */ - return 0; - - o = 1; - ptr++; - } - - *s = ptr; - return o; -} - -/* sanitize given string and replace all invalid UTF-8 sequences with "?" */ -char * utf8(const char *s, unsigned int l) -{ - struct template_buffer *buf = buf_init(l); - unsigned char *ptr = (unsigned char *)s; - unsigned int v, o; - - if (!buf) - return NULL; - - for (o = 0; o < l; o++) - { - /* ascii char */ - if ((*ptr >= 0x01) && (*ptr <= 0x7F)) - { - if (!buf_putchar(buf, (char)*ptr++)) - break; - } - - /* invalid byte or multi byte sequence */ - else - { - if (!(v = _validate_utf8(&ptr, l - o, buf))) - break; - - o += (v - 1); - } - } - - return buf_destroy(buf); -} - -/* Sanitize given string and strip all invalid XML bytes - * Validate UTF-8 sequences - * Escape XML control chars */ -char * pcdata(const char *s, unsigned int l) -{ - struct template_buffer *buf = buf_init(l); - unsigned char *ptr = (unsigned char *)s; - unsigned int o, v; - char esq[8]; - int esl; - - if (!buf) - return NULL; - - for (o = 0; o < l; o++) - { - /* Invalid XML bytes */ - if (((*ptr >= 0x00) && (*ptr <= 0x08)) || - ((*ptr >= 0x0B) && (*ptr <= 0x0C)) || - ((*ptr >= 0x0E) && (*ptr <= 0x1F)) || - (*ptr == 0x7F)) - { - ptr++; - } - - /* Escapes */ - else if ((*ptr == 0x26) || - (*ptr == 0x27) || - (*ptr == 0x22) || - (*ptr == 0x3C) || - (*ptr == 0x3E)) - { - esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - - if (!buf_append(buf, esq, esl)) - break; - - ptr++; - } - - /* ascii char */ - else if (*ptr <= 0x7F) - { - buf_putchar(buf, (char)*ptr++); - } - - /* multi byte sequence */ - else - { - if (!(v = _validate_utf8(&ptr, l - o, buf))) - break; - - o += (v - 1); - } - } - - return buf_destroy(buf); -} - -char * striptags(const char *s, unsigned int l) -{ - struct template_buffer *buf = buf_init(l); - unsigned char *ptr = (unsigned char *)s; - unsigned char *end = ptr + l; - unsigned char *tag; - unsigned char prev; - char esq[8]; - int esl; - - for (prev = ' '; ptr < end; ptr++) - { - if ((*ptr == '<') && ((ptr + 2) < end) && - ((*(ptr + 1) == '/') || isalpha(*(ptr + 1)))) - { - for (tag = ptr; tag < end; tag++) - { - if (*tag == '>') - { - if (!isspace(prev)) - buf_putchar(buf, ' '); - - ptr = tag; - prev = ' '; - break; - } - } - } - else if (isspace(*ptr)) - { - if (!isspace(prev)) - buf_putchar(buf, *ptr); - - prev = *ptr; - } - else - { - switch(*ptr) - { - case '"': - case '\'': - case '<': - case '>': - case '&': - esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - buf_append(buf, esq, esl); - break; - - default: - buf_putchar(buf, *ptr); - break; - } - - prev = *ptr; - } - } - - return buf_destroy(buf); -} - -void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, - int escape_xml) -{ - int esl; - char esq[8]; - char *ptr; - - for (ptr = (char *)s; ptr < (s + l); ptr++) - { - switch (*ptr) - { - case '\\': - buf_append(out, "\\\\", 2); - break; - - case '"': - if (escape_xml) - buf_append(out, """, 5); - else - buf_append(out, "\\\"", 2); - break; - - case '\n': - buf_append(out, "\\n", 2); - break; - - case '\'': - case '&': - case '<': - case '>': - if (escape_xml) - { - esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - buf_append(out, esq, esl); - break; - } - - default: - buf_putchar(out, *ptr); - } - } -} - -void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, - int escape_xml) -{ - int trlen, idlen = l, ctxtlen = 0, esc = 0; - const char *p, *msgid = s, *msgctxt = NULL; - char *tr; - - for (p = s; p < s + l; p++) { - if (esc) { - esc = 0; - } - else if (*p == '\\') { - esc = 1; - } - else if (*p == '|') { - idlen = p - s; - msgctxt = p + 1; - ctxtlen = s + l - msgctxt; - break; - } - } - - if (!lmo_translate_ctxt(msgid, idlen, msgctxt, ctxtlen, &tr, &trlen)) - luastr_escape(out, tr, trlen, escape_xml); - else - luastr_escape(out, s, l, escape_xml); -} diff --git a/modules/luci-base/src/template_utils.h b/modules/luci-base/src/template_utils.h deleted file mode 100644 index 32a79f93bc..0000000000 --- a/modules/luci-base/src/template_utils.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * LuCI Template - Utility header - * - * Copyright (C) 2010-2012 Jo-Philipp Wich <jow@openwrt.org> - * - * 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_UTILS_H_ -#define _TEMPLATE_UTILS_H_ - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - - -/* buffer object */ -struct template_buffer { - char *data; - char *dptr; - unsigned int size; - unsigned int fill; -}; - -struct template_buffer * buf_init(int size); -int buf_grow(struct template_buffer *buf, int size); -int buf_putchar(struct template_buffer *buf, char c); -int buf_append(struct template_buffer *buf, const char *s, int len); -int buf_length(struct template_buffer *buf); -char * buf_destroy(struct template_buffer *buf); - -char * utf8(const char *s, unsigned int l); -char * pcdata(const char *s, unsigned int l); -char * striptags(const char *s, unsigned int l); - -void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); -void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); - -#endif diff --git a/modules/luci-base/ucode/controller/admin/index.uc b/modules/luci-base/ucode/controller/admin/index.uc new file mode 100644 index 0000000000..16a74abc46 --- /dev/null +++ b/modules/luci-base/ucode/controller/admin/index.uc @@ -0,0 +1,158 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { load_catalog, change_catalog, get_translations } from 'luci.core'; + +const ubus_types = [ + null, + 'array', + 'object', + 'string', + null, // INT64 + 'number', + null, // INT16, + 'boolean', + 'double' +]; + + +function ubus_reply(id, data, code, errmsg) { + const reply = { jsonrpc: '2.0', id }; + + if (errmsg) + reply.error = { code, message: errmsg }; + else if (type(code) == 'object') + reply.result = code; + else + reply.result = [ code, data ]; + + return reply; +} + +function ubus_access(sid, obj, fun) { + return (ubus.call('session', 'access', { + ubus_rpc_session: sid, + scope: 'ubus', + object: obj, + function: fun + })?.access == true); +} + +function ubus_request(req) { + if (type(req?.method) != 'string' || req?.jsonrpc != '2.0' || req?.id == null) + return ubus_reply(null, null, -32600, 'Invalid request'); + + if (req.method == 'call') { + if (type(req?.params) != 'array' || length(req.params) < 3) + return ubus_reply(null, null, -32600, 'Invalid parameters'); + + let sid = req.params[0], + obj = req.params[1], + fun = req.params[2], + arg = req.params[3] ?? {}; + + if (type(arg) != 'object' || exists(arg, 'ubus_rpc_session')) + return ubus_reply(req.id, null, -32602, 'Invalid parameters'); + + if (sid == '00000000000000000000000000000000' && ctx.authsession) + sid = ctx.authsession; + + if (!ubus_access(sid, obj, fun)) + return ubus_reply(req.id, null, -32002, 'Access denied'); + + arg.ubus_rpc_session = sid; + + + // clear error + ubus.error(); + + const res = ubus.call(obj, fun, arg); + + return ubus_reply(req.id, res, ubus.error(true) ?? 0); + } + + if (req.method == 'list') { + if (req?.params == null || (type(req.params) == 'array' && length(req.params) == 0)) { + return ubus_reply(req.id, null, ubus.list()); + } + else if (type(req.params) == 'array') { + const rv = {}; + + for (let param in req.params) { + if (type(param) != 'string') + return ubus_reply(req.id, null, -32602, 'Invalid parameters'); + + for (let m, p in ubus.list(param)?.[0]) { + for (let pn, pt in p) { + rv[param] ??= {}; + rv[param][m] ??= {}; + rv[param][m][pn] = ubus_types[pt] ?? 'unknown'; + } + } + } + + return ubus_reply(req.id, null, rv); + } + else { + return ubus_reply(req.id, null, -32602, 'Invalid parameters') + } + } + + return ubus_reply(req.id, null, -32601, 'Method not found') +} + + +return { + action_ubus: function() { + let request; + + try { request = json(http.content()); } + catch { request = null; } + + http.prepare_content('application/json; charset=UTF-8'); + + if (type(request) == 'object') + http.write_json(ubus_request(request)); + else if (type(request) == 'array') + http.write_json(map(request, ubus_request)); + else + http.write_json(ubus_reply(null, null, -32700, 'Parse error')) + }, + + action_translations: function(reqlang) { + if (reqlang != null && reqlang != dispatcher.lang) { + load_catalog(reqlang, '/usr/lib/lua/luci/i18n'); + change_catalog(reqlang); + } + + http.prepare_content('application/javascript; charset=UTF-8'); + http.write('window.TR={'); + + get_translations((key, val) => http.write(sprintf('"%08x":%J,', key, val))); + + http.write('};'); + }, + + action_logout: function() { + const url = dispatcher.build_url(); + + if (ctx.authsession) { + ubus.call('session', 'destroy', { ubus_rpc_session: ctx.authsession }); + + if (http.getenv('HTTPS') == 'on') + http.header('Set-Cookie', `sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`); + + http.header('Set-Cookie', `sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`); + } + + http.redirect(url); + }, + + action_menu: function() { + const session = dispatcher.is_authenticated({ methods: [ 'cookie:sysauth_https', 'cookie:sysauth_http' ] }); + const menu = dispatcher.menu_json(session?.acls ?? {}) ?? {}; + + http.prepare_content('application/json; charset=UTF-8'); + http.write_json(menu); + } +}; diff --git a/modules/luci-base/ucode/controller/admin/uci.uc b/modules/luci-base/ucode/controller/admin/uci.uc new file mode 100644 index 0000000000..c38a42b10b --- /dev/null +++ b/modules/luci-base/ucode/controller/admin/uci.uc @@ -0,0 +1,150 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { STATUS_NO_DATA, STATUS_PERMISSION_DENIED } from 'ubus'; + +let last_ubus_error; + +const ubus_error_map = [ + 200, 'OK', + 400, 'Invalid command', + 400, 'Invalid argument', + 404, 'Method not found', + 404, 'Not found', + 204, 'No data', + 403, 'Permission denied', + 504, 'Timeout', + 500, 'Not supported', + 500, 'Unknown error', + 503, 'Connection failed', + 500, 'Out of memory', + 400, 'Parse error', + 500, 'System error', +]; + +function ubus_call(object, method, args) { + ubus.error(); // clear previous error + + let res = ubus.call(object, method, args); + + last_ubus_error = ubus.error(true); + + return res ?? !last_ubus_error; +} + +function ubus_state_to_http(err) { + let code = ubus_error_map[(err << 1) + 0] ?? 200; + let msg = ubus_error_map[(err << 1) + 1] ?? 'OK'; + + http.status(code, msg); + + if (code != 204) { + http.prepare_content('text/plain'); + http.write(msg); + } +} + +function uci_apply(rollback) { + if (rollback) { + const timeout = +(config?.apply?.rollback ?? 90) || 0; + const success = ubus_call('uci', 'apply', { + ubus_rpc_session: ctx.authsession, + timeout: max(timeout, 90), + rollback: true + }); + + if (success) { + const token = dispatcher.randomid(16); + + ubus.call('session', 'set', { + ubus_rpc_session: '00000000000000000000000000000000', + values: { + rollback: { + token, + session: ctx.authsession, + timeout: time() + timeout + } + } + }); + + return token; + } + + return null; + } + else { + let changes = ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes; + + for (let config in changes) + if (!ubus_call('uci', 'commit', { ubus_rpc_session: ctx.authsession, config })) + return false; + + return ubus_call('uci', 'apply', { + ubus_rpc_session: ctx.authsession, + rollback: false + }); + } +} + +function uci_confirm(token) { + const data = ubus.call('session', 'get', { + ubus_rpc_session: '00000000000000000000000000000000', + keys: [ 'rollback' ] + })?.values?.rollback; + + if (type(data?.token) != 'string' || type(data?.session) != 'string' || + type(data?.timeout) != 'int' || data.timeout < time()) { + last_ubus_error = STATUS_NO_DATA; + + return false; + } + + if (token != data.token) { + last_ubus_error = STATUS_PERMISSION_DENIED; + + return false; + } + + if (!ubus_call('uci', 'confirm', { ubus_rpc_session: data.session })) + return false; + + ubus_call('session', 'set', { + ubus_rpc_session: '00000000000000000000000000000000', + values: { rollback: {} } + }); + + return true; +} + + +return { + action_apply_rollback: function() { + const token = uci_apply(true); + + if (token) { + http.prepare_content('application/json; charset=UTF-8'); + http.write_json({ token }); + } + else { + ubus_state_to_http(last_ubus_error); + } + }, + + action_apply_unchecked: function() { + uci_apply(false); + ubus_state_to_http(last_ubus_error); + }, + + action_confirm: function() { + uci_confirm(http.formvalue('token')); + ubus_state_to_http(last_ubus_error); + }, + + action_revert: function() { + for (let config in ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes) + if (!ubus_call('uci', 'revert', { ubus_rpc_session: ctx.authsession, config })) + break; + + ubus_state_to_http(last_ubus_error); + } +}; diff --git a/modules/luci-base/ucode/dispatcher.uc b/modules/luci-base/ucode/dispatcher.uc new file mode 100644 index 0000000000..84eff71d3a --- /dev/null +++ b/modules/luci-base/ucode/dispatcher.uc @@ -0,0 +1,942 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { open, stat, glob, lsdir, unlink, basename } from 'fs'; +import { striptags, entityencode } from 'html'; +import { connect } from 'ubus'; +import { cursor } from 'uci'; +import { rand } from 'math'; + +import { hash, load_catalog, change_catalog, translate, ntranslate, getuid } from 'luci.core'; +import { revision as luciversion, branch as luciname } from 'luci.version'; +import { default as LuCIRuntime } from 'luci.runtime'; +import { urldecode } from 'luci.http'; + +let ubus = connect(); +let uci = cursor(); + +let indexcache = "/tmp/luci-indexcache"; + +let http, runtime, tree, luabridge; + +function error404(msg) { + http.status(404, 'Not Found'); + + try { + runtime.render('error404', { message: msg ?? 'Not found' }); + } + catch { + http.header('Content-Type', 'text/plain; charset=UTF-8'); + http.write(msg ?? 'Not found'); + } + + return false; +} + +function error500(msg, ex) { + if (!http.eoh) { + http.status(500, 'Internal Server Error'); + http.header('Content-Type', 'text/html; charset=UTF-8'); + } + + try { + runtime.render('error500', { + title: ex?.type ?? 'Runtime exception', + message: replace( + msg, + /(\s)((\/[A-Za-z0-9_.-]+)+:\d+|\[string "[^"]+"\]:\d+)/g, + '$1<code>$2</code>' + ), + exception: ex + }); + } + catch { + http.write('<!--]]>--><!--\'>--><!--">-->\n'); + http.write(`<p>${trim(ex)}</p>\n`); + + if (ex) { + http.write(`<p>${trim(ex.message)}</p>\n`); + http.write(`<pre>${trim(ex.stacktrace[0].context)}</pre>\n`); + } + } + + exit(0); +} + +function load_luabridge(optional) { + if (luabridge == null) { + try { + luabridge = require('lua'); + } + catch (ex) { + luabridge = false; + + if (!optional) + error500('No Lua runtime installed'); + } + } + + return luabridge; +} + +function determine_request_language() { + let lang = uci.get('luci', 'main', 'lang') || 'auto'; + + if (lang == 'auto') { + for (let tag in split(http.getenv('HTTP_ACCEPT_LANGUAGE'), ',')) { + tag = split(trim(split(tag, ';')?.[0]), '-'); + + if (tag) { + let cc = tag[1] ? `${tag[0]}_${lc(tag[1])}` : null; + + if (cc && uci.get('luci', 'languages', cc)) { + lang = cc; + break; + } + else if (uci.get('luci', 'languages', tag[0])) { + lang = tag[0]; + break; + } + } + } + } + + if (lang == 'auto') + lang = 'en'; + + if (load_catalog(lang, '/usr/lib/lua/luci/i18n')) + change_catalog(lang); + + return lang; +} + +function determine_version() { + let res = { luciname, luciversion }; + + for (let f = open("/etc/os-release"), l = f?.read?.("line"); l; l = f.read?.("line")) { + let kv = split(l, '=', 2); + + switch (kv[0]) { + case 'NAME': + res.distname = trim(kv[1], '"\' \n'); + break; + + case 'VERSION': + res.distversion = trim(kv[1], '"\' \n'); + break; + + case 'HOME_URL': + res.disturl = trim(kv[1], '"\' \n'); + break; + + case 'BUILD_ID': + res.distrevision = trim(kv[1], '"\' \n'); + break; + } + } + + return res; +} + +function read_jsonfile(path, defval) { + let rv; + + try { + rv = json(open(path, "r")); + } + catch (e) { + rv = defval; + } + + return rv; +} + +function read_cachefile(file, reader) { + let euid = getuid(), + fstat = stat(file), + fuid = fstat?.uid, + perm = fstat?.perm; + + if (euid != fuid || + perm?.group_read || perm?.group_write || perm?.group_exec || + perm?.other_read || perm?.other_write || perm?.other_exec) + return null; + + return reader(file); +} + +function check_fs_depends(spec) { + for (let path, kind in spec) { + if (kind == 'directory') { + if (!length(lsdir(path))) + return false; + } + else if (kind == 'executable') { + let fstat = stat(path); + + if (fstat?.type != 'file' || fstat?.user_exec == false) + return false; + } + else if (kind == 'file') { + let fstat = stat(path); + + if (fstat?.type != 'file') + return false; + } + } + + return true; +} + +function check_uci_depends_options(conf, s, opts) { + if (type(opts) == 'string') { + return (s['.type'] == opts); + } + else if (opts === true) { + for (let option, value in s) + if (ord(option) != 46) + return true; + } + else if (type(opts) == 'object') { + for (let option, value in opts) { + let sval = s[option]; + + if (type(sval) == 'array') { + if (!(value in sval)) + return false; + } + else if (value === true) { + if (sval == null) + return false; + } + else { + if (sval != value) + return false; + } + } + } + + return true; +} + +function check_uci_depends_section(conf, sect) { + for (let section, options in sect) { + let stype = match(section, /^@([A-Za-z0-9_-]+)$/); + + if (stype) { + let found = false; + + uci.load(conf); + uci.foreach(conf, stype[1], (s) => { + if (check_uci_depends_options(conf, s, options)) { + found = true; + return false; + } + }); + + if (!found) + return false; + } + else { + let s = uci.get_all(conf, section); + + if (!s || !check_uci_depends_options(conf, s, options)) + return false; + } + } + + return true; +} + +function check_uci_depends(conf) { + for (let config, values in conf) { + if (values == true) { + let found = false; + + uci.load(config); + uci.foreach(config, null, () => { found = true }); + + if (!found) + return false; + } + else if (type(values) == 'object') { + if (!check_uci_depends_section(config, values)) + return false; + } + } + + return true; +} + +function check_depends(spec) { + if (type(spec?.depends?.fs) in ['array', 'object']) { + let satisfied = false; + let alternatives = (type(spec.depends.fs) == 'array') ? spec.depends.fs : [ spec.depends.fs ]; + + for (let alternative in alternatives) { + if (check_fs_depends(alternative)) { + satisfied = true; + break; + } + } + + if (!satisfied) + return false; + } + + if (type(spec?.depends?.uci) in ['array', 'object']) { + let satisfied = false; + let alternatives = (type(spec.depends.uci) == 'array') ? spec.depends.uci : [ spec.depends.uci ]; + + for (let alternative in alternatives) { + if (check_uci_depends(alternative)) { + satisfied = true; + break; + } + } + + if (!satisfied) + return false; + } + + return true; +} + +function check_acl_depends(require_groups, groups) { + if (length(require_groups)) { + let writable = false; + + for (let group in require_groups) { + let read = ('read' in groups?.[group]); + let write = ('write' in groups?.[group]); + + if (!read && !write) + return null; + + if (write) + writable = true; + } + + return writable; + } + + return true; +} + +function hash_filelist(files) { + let hashval = 0x1b756362; + + for (let file in files) { + let st = stat(file); + + if (st) + hashval = hash(sprintf("%x|%x|%x", st.ino, st.mtime, st.size), hashval); + } + + return hashval; +} + +function build_pagetree() { + let tree = { action: { type: 'firstchild' } }; + + let schema = { + action: 'object', + auth: 'object', + cors: 'bool', + depends: 'object', + order: 'int', + setgroup: 'string', + setuser: 'string', + title: 'string', + wildcard: 'bool', + firstchild_ineligible: 'bool' + }; + + let files = glob('/usr/share/luci/menu.d/*.json', '/usr/lib/lua/luci/controller/*.lua', '/usr/lib/lua/luci/controller/*/*.lua'); + let cachefile; + + if (indexcache) { + cachefile = sprintf('%s.%08x.json', indexcache, hash_filelist(files)); + + let res = read_cachefile(cachefile, read_jsonfile); + + if (res) + return res; + + for (let path in glob(indexcache + '.*.json')) + unlink(path); + } + + for (let file in files) { + let data; + + if (substr(file, -5) == '.json') + data = read_jsonfile(file); + else if (load_luabridge(true)) + data = runtime.call('luci.dispatcher', 'process_lua_controller', file); + else + warn(`Lua controller ${file} present but no Lua runtime installed.\n`); + + if (type(data) == 'object') { + for (let path, spec in data) { + if (type(spec) == 'object') { + let node = tree; + + for (let s in match(path, /[^\/]+/g)) { + if (s[0] == '*') { + node.wildcard = true; + break; + } + + node.children ??= {}; + node.children[s[0]] ??= {}; + node = node.children[s[0]]; + } + + if (node !== tree) { + for (let k, t in schema) + if (type(spec[k]) == t) + node[k] = spec[k]; + + node.satisfied = check_depends(spec); + } + } + } + } + } + + if (cachefile) { + let fd = open(cachefile, 'w', 0600); + + if (fd) { + fd.write(tree); + fd.close(); + } + } + + return tree; +} + +function menu_json(acl) { + tree ??= build_pagetree(); + + return tree; +} + +function ctx_append(ctx, name, node) { + ctx.path ??= []; + push(ctx.path, name); + + ctx.acls ??= []; + push(ctx.acls, ...(node?.depends?.acl || [])); + + ctx.auth = node.auth || ctx.auth; + ctx.cors = node.cors || ctx.cors; + ctx.suid = node.setuser || ctx.suid; + ctx.sgid = node.setgroup || ctx.sgid; + + return ctx; +} + +function session_retrieve(sid, allowed_users) { + let sdat = ubus.call("session", "get", { ubus_rpc_session: sid }); + let sacl = ubus.call("session", "access", { ubus_rpc_session: sid }); + + if (type(sdat?.values?.token) == 'string' && + (!length(allowed_users) || sdat?.values?.username in allowed_users)) { + // uci:set_session_id(sid) + return { + sid, + data: sdat.values, + acls: length(sacl) ? sacl : {} + }; + } + + return null; +} + +function randomid(num_bytes) { + let bytes = []; + + while (num_bytes-- > 0) + push(bytes, sprintf('%02x', rand() % 256)); + + return join('', bytes); +} + +function syslog(prio, msg) { + warn(sprintf("[%s] %s\n", prio, msg)); +} + +function session_setup(user, pass, path) { + let timeout = uci.get('luci', 'sauth', 'sessiontime'); + let login = ubus.call("session", "login", { + username: user, + password: pass, + timeout: timeout ? +timeout : null + }); + + if (type(login?.ubus_rpc_session) == 'string') { + ubus.call("session", "set", { + ubus_rpc_session: login.ubus_rpc_session, + values: { token: randomid(16) } + }); + syslog("info", sprintf("luci: accepted login on /%s for %s from %s", + join('/', path), user || "?", http.getenv("REMOTE_ADDR") || "?")); + + return session_retrieve(login.ubus_rpc_session); + } + + syslog("info", sprintf("luci: failed login on /%s for %s from %s", + join('/', path), user || "?", http.getenv("REMOTE_ADDR") || "?")); +} + +function check_authentication(method) { + let m = match(method, /^([[:alpha:]]+):(.+)$/); + let sid; + + switch (m?.[1]) { + case 'cookie': + sid = http.getcookie(m[2]); + break; + + case 'param': + sid = http.formvalue(m[2]); + break; + + case 'query': + sid = http.formvalue(m[2], true); + break; + } + + return sid ? session_retrieve(sid) : null; +} + +function is_authenticated(auth) { + for (let method in auth?.methods) { + let session = check_authentication(method); + + if (session) + return session; + } + + return null; +} + +function node_weight(node) { + let weight = min(node.order ?? 9999, 9999); + + if (node.auth?.login) + weight += 10000; + + return weight; +} + +function clone(src) { + switch (type(src)) { + case 'array': + return map(src, clone); + + case 'object': + let dest = {}; + + for (let k, v in src) + dest[k] = clone(v); + + return dest; + + default: + return src; + } +} + +function resolve_firstchild(node, session, login_allowed, ctx) { + let candidate, candidate_ctx; + + for (let name, child in node.children) { + if (!child.satisfied) + continue; + + if (!session) + session = is_authenticated(node.auth); + + let cacl = child.depends?.acl; + let login = login_allowed || child.auth?.login; + + if (login || check_acl_depends(cacl, session?.acls?.["access-group"]) != null) { + if (child.title && type(child.action) == "object") { + let child_ctx = ctx_append(clone(ctx), name, child); + if (child.action.type == "firstchild") { + if (!candidate || node_weight(candidate) > node_weight(child)) { + let have_grandchild = resolve_firstchild(child, session, login, child_ctx); + if (have_grandchild) { + candidate = child; + candidate_ctx = child_ctx; + } + } + } + else if (!child.firstchild_ineligible) { + if (!candidate || node_weight(candidate) > node_weight(child)) { + candidate = child; + candidate_ctx = child_ctx; + } + } + } + } + } + + if (!candidate) + return false; + + for (let k, v in candidate_ctx) + ctx[k] = v; + + return true; +} + +function resolve_page(tree, request_path) { + let node = tree; + let login = false; + let session = null; + let ctx = {}; + + for (let i, s in request_path) { + node = node.children?.[s]; + + if (!node?.satisfied) + break; + + ctx_append(ctx, s, node); + + if (!session) + session = is_authenticated(node.auth); + + if (!login && node.auth?.login) + login = true; + + if (node.wildcard) { + ctx.request_args = []; + ctx.request_path = ctx.path ? [ ...ctx.path ] : []; + + while (++i < length(request_path)) { + push(ctx.request_path, request_path[i]); + push(ctx.request_args, request_path[i]); + } + + break; + } + } + + if (node?.action?.type == 'firstchild') + resolve_firstchild(node, session, login, ctx); + + ctx.acls ??= {}; + ctx.path ??= []; + ctx.request_args ??= []; + ctx.request_path ??= request_path ? [ ...request_path ] : []; + + ctx.authsession = session?.sid; + ctx.authtoken = session?.data?.token; + ctx.authuser = session?.data?.username; + ctx.authacl = session?.acls; + + node = tree; + + for (let s in ctx.path) { + node = node.children[s]; + assert(node, "Internal node resolve error"); + } + + return { node, ctx, session }; +} + +function require_post_security(target, args) { + if (target?.type == 'arcombine') + return require_post_security(length(args) ? target?.targets?.[1] : target?.targets?.[0], args); + + if (type(target?.post) == 'object') { + for (let param_name, required_val in target.post) { + let request_val = http.formvalue(param_name); + + if ((type(required_val) == 'string' && request_val != required_val) || + (required_val == true && request_val == null)) + return false; + } + + return true; + } + + return (target?.post == true); +} + +function test_post_security(authtoken) { + if (http.getenv("REQUEST_METHOD") != "POST") { + http.status(405, "Method Not Allowed"); + http.header("Allow", "POST"); + + return false; + } + + if (http.formvalue("token") != authtoken) { + http.status(403, "Forbidden"); + runtime.render("csrftoken"); + + return false; + } + + return true; +} + +function build_url(...path) { + let url = [ http.getenv('SCRIPT_NAME') ?? '' ]; + + for (let p in path) + if (match(p, /^[A-Za-z0-9_%.\/,;-]+$/)) + push(url, '/', p); + + if (length(url) == 1) + push(url, '/'); + + return join('', url); +} + +function lookup(...segments) { + let node = menu_json(); + let path = []; + + for (let segment in segments) + for (let name in split(segment, '/')) + push(path, name); + + for (let name in path) { + node = node.children[name]; + + if (!node) + return null; + + if (node.leaf) + break; + } + + return { node, url: build_url(...path) }; +} + +function rollback_pending() { + const now = time(); + const rv = ubus.call('session', 'get', { + ubus_rpc_session: '00000000000000000000000000000000', + keys: [ 'rollback' ] + }); + + if (type(rv?.values?.rollback?.token) != 'string' || + type(rv?.values?.rollback?.session) != 'string' || + type(rv?.values?.rollback?.timeout) != 'int' || + rv.values.rollback.timeout <= now) + return false; + + return { + remaining: rv.values.rollback.timeout - now, + session: rv.values.rollback.session, + token: rv.values.rollback.token + }; +} + +let dispatch; + +function run_action(request_path, lang, tree, resolved, action) { + switch (action?.type) { + case 'template': + runtime.render(action.path, {}); + break; + + case 'view': + runtime.render('view', { view: action.path }); + break; + + case 'call': + http.write(render(() => { + runtime.call(action.module, action.function, + ...(action.parameters ?? []), + ...resolved.ctx.request_args + ); + })); + break; + + case 'function': + const mod = require(action.module); + + assert(type(mod[action.function]) == 'function', + `Module '${action.module}' does not export function '${action.function}'`); + + http.write(render(() => { + call(mod[action.function], mod, runtime.env, + ...(action.parameters ?? []), + ...resolved.ctx.request_args + ); + })); + break; + + case 'alias': + dispatch(http, [ ...split(action.path, '/'), ...resolved.ctx.request_args ]); + break; + + case 'rewrite': + dispatch(http, [ + ...splice([ ...request_path ], 0, action.remove), + ...split(action.path, '/'), + ...resolved.ctx.request_args + ]); + break; + + case 'firstchild': + if (!length(tree.children)) + error404("No root node was registered, this usually happens if no module was installed.\n" + + "Install luci-mod-admin-full and retry. " + + "If the module is already installed, try removing the /tmp/luci-indexcache file."); + else + error404(`No page is registered at '/${join("/", resolved.ctx.request_path)}'.\n` + + "If this url belongs to an extension, make sure it is properly installed.\n" + + "If the extension was recently installed, try removing the /tmp/luci-indexcache file."); + break; + + default: + error500(`Unhandled action type ${action?.type ?? '?'}`); + } +} + +dispatch = function(_http, path) { + http = _http; + + let version = determine_version(); + let lang = determine_request_language(); + + runtime = LuCIRuntime({ + http, + ubus, + uci, + ctx: {}, + version, + config: { + main: uci.get_all('luci', 'main') ?? {}, + apply: uci.get_all('luci', 'apply') ?? {} + }, + dispatcher: { + rollback_pending, + is_authenticated, + load_luabridge, + lookup, + menu_json, + build_url, + randomid, + error404, + error500, + lang + }, + striptags, + entityencode, + _: (...args) => translate(...args) ?? args[0], + N_: (...args) => ntranslate(...args) ?? (n[0] == 1 ? n[1] : n[2]), + }); + + try { + let menu = menu_json(); + + path ??= map(match(http.getenv('PATH_INFO'), /[^\/]+/g), m => m[0]); + + let resolved = resolve_page(menu, path); + + runtime.env.ctx = resolved.ctx; + runtime.env.node = resolved.node; + + if (length(resolved.ctx.auth)) { + let session = is_authenticated(resolved.ctx.auth); + + if (!session && resolved.ctx.auth.login) { + let user = http.getenv('HTTP_AUTH_USER'); + let pass = http.getenv('HTTP_AUTH_PASS'); + + if (user == null && pass == null) { + user = http.formvalue('luci_username'); + pass = http.formvalue('luci_password'); + } + + if (user != null && pass != null) + session = session_setup(user, pass, resolved.ctx.request_path); + + if (!session) { + resolved.ctx.path = []; + + http.status(403, 'Forbidden'); + http.header('X-LuCI-Login-Required', 'yes'); + + let scope = { duser: 'root', fuser: user }; + + try { + runtime.render(`themes/${basename(runtime.env.media)}/sysauth`, scope); + } + catch (e) { + runtime.render('sysauth', scope); + } + + return; + } + + let cookie_name = (http.getenv('HTTPS') == 'on') ? 'sysauth_https' : 'sysauth_http', + cookie_secure = (http.getenv('HTTPS') == 'on') ? '; secure' : ''; + + http.header('Set-Cookie', `${cookie_name}=${session.sid}; path=${build_url()}; SameSite=strict; HttpOnly${cookie_secure}`); + http.redirect(build_url(...resolved.ctx.request_path)); + + return; + } + + if (!session) { + http.status(403, 'Forbidden'); + http.header('X-LuCI-Login-Required', 'yes'); + + return; + } + + resolved.ctx.authsession ??= session.sid; + resolved.ctx.authtoken ??= session.data?.token; + resolved.ctx.authuser ??= session.data?.username; + resolved.ctx.authacl ??= session.acls; + } + + if (length(resolved.ctx.acls)) { + let perm = check_acl_depends(resolved.ctx.acls, resolved.ctx.authacl?.['access-group']); + + if (perm == null) { + http.status(403, 'Forbidden'); + + return; + } + + if (resolved.node) + resolved.node.readonly = !perm; + } + + let action = resolved.node.action; + + if (action?.type == 'arcombine') + action = length(resolved.ctx.request_args) ? action.targets?.[1] : action.targets?.[0]; + + if (resolved.ctx.cors && http.getenv('REQUEST_METHOD') == 'OPTIONS') { + http.status(200, 'OK'); + http.header('Access-Control-Allow-Origin', http.getenv('HTTP_ORIGIN') ?? '*'); + http.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + + return; + } + + if (require_post_security(action) && !test_post_security(resolved.ctx.authtoken)) + return; + + run_action(path, lang, menu, resolved, action); + } + catch (ex) { + error500('Unhandled exception during request dispatching', ex); + } +}; + +export default dispatch; diff --git a/modules/luci-base/ucode/http.uc b/modules/luci-base/ucode/http.uc new file mode 100644 index 0000000000..b464497eac --- /dev/null +++ b/modules/luci-base/ucode/http.uc @@ -0,0 +1,574 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { + urlencode as _urlencode, + urldecode as _urldecode, + urlencoded_parser, multipart_parser, header_attribute, + ENCODE_IF_NEEDED, ENCODE_FULL, DECODE_IF_NEEDED, DECODE_PLUS +} from 'lucihttp'; + +import { + error as fserror, + stdin, stdout, mkstemp +} from 'fs'; + +// luci.http module scope +export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size + +// Decode a mime encoded http message body with multipart/form-data +// 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) +export function mimedecode_message_body(src, msg, file_cb) { + let len = 0, maxlen = +msg.env.CONTENT_LENGTH; + let header, field, parser; + + parser = multipart_parser(msg.env.CONTENT_TYPE, function(what, buffer, length) { + if (what == parser.PART_INIT) { + field = {}; + } + else if (what == parser.HEADER_NAME) { + header = lc(buffer); + } + else if (what == parser.HEADER_VALUE && header) { + if (lc(header) == 'content-disposition' && + header_attribute(buffer, null) == 'form-data') { + field.name = header_attribute(buffer, 'name'); + field.file = header_attribute(buffer, 'filename'); + field[1] = field.file; + } + + field.headers = field.headers || {}; + field.headers[header] = buffer; + } + else if (what == parser.PART_BEGIN) { + return !field.file; + } + else if (what == parser.PART_DATA && field.name && length > 0) { + if (field.file) { + if (file_cb) { + file_cb(field, buffer, false); + + msg.params[field.name] = msg.params[field.name] || field; + } + else { + if (!field.fd) + field.fd = mkstemp(field.name); + + if (field.fd) { + field.fd.write(buffer); + msg.params[field.name] = msg.params[field.name] || field; + } + } + } + else { + field.value = buffer; + } + } + else if (what == parser.PART_END && field.name) { + if (field.file && msg.params[field.name]) { + if (file_cb) + file_cb(field, '', true); + else if (field.fd) + field.fd.seek(0); + } + else { + let val = msg.params[field.name]; + + if (type(val) == 'array') + push(val, field.value || ''); + else if (val != null) + msg.params[field.name] = [ val, field.value || '' ]; + else + msg.params[field.name] = field.value || ''; + } + + field = null; + } + else if (what == parser.ERROR) { + err = buffer; + } + + return true; + }, HTTP_MAX_CONTENT); + + while (true) { + let chunk = src(); + + len += length(chunk); + + if (maxlen && len > maxlen + 2) + die('Message body size exceeds Content-Length'); + + if (!parser.parse(chunk)) + die(err); + + if (chunk == null) + break; + } +}; + +// Decode an urlencoded http message body with application/x-www-urlencoded +// 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. +export function urldecode_message_body(src, msg) { + let len = 0, maxlen = +msg.env.CONTENT_LENGTH; + let err, name, value, parser; + + parser = urlencoded_parser(function (what, buffer, length) { + if (what == parser.TUPLE) { + name = null; + value = null; + } + else if (what == parser.NAME) { + name = _urldecode(buffer, DECODE_PLUS); + } + else if (what == parser.VALUE && name) { + let val = msg.params[name]; + + if (type(val) == 'array') + push(val, _urldecode(buffer, DECODE_PLUS) || ''); + else if (val != null) + msg.params[name] = [ val, _urldecode(buffer, DECODE_PLUS) || '' ]; + else + msg.params[name] = _urldecode(buffer, DECODE_PLUS) || ''; + } + else if (what == parser.ERROR) { + err = buffer; + } + + return true; + }, HTTP_MAX_CONTENT); + + while (true) { + let chunk = src(); + + len += length(chunk); + + if (maxlen && len > maxlen + 2) + die('Message body size exceeds Content-Length'); + + if (!parser.parse(chunk)) + die(err); + + if (chunk == null) + break; + } +}; + +// 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. +export function parse_message_body(src, msg, filecb) { + if (msg.env.CONTENT_LENGTH || msg.env.REQUEST_METHOD == 'POST') { + let ctype = header_attribute(msg.env.CONTENT_TYPE, null); + + // Is it multipart/mime ? + if (ctype == 'multipart/form-data') + return mimedecode_message_body(src, msg, filecb); + + // Is it application/x-www-form-urlencoded ? + else if (ctype == 'application/x-www-form-urlencoded') + return urldecode_message_body(src, msg); + + // Unhandled encoding + // If a file callback is given then feed it chunk by chunk, else + // store whole buffer in message.content + let sink; + + // If we have a file callback then feed it + if (type(filecb) == 'function') { + let meta = { + name: 'raw', + encoding: msg.env.CONTENT_TYPE + }; + + sink = (chunk) => { + if (chunk != null) + return filecb(meta, chunk, false); + else + return filecb(meta, null, true); + }; + } + + // ... else append to .content + else { + let chunks = [], len = 0; + + sink = (chunk) => { + len += length(chunk); + + if (len > HTTP_MAX_CONTENT) + die('POST data exceeds maximum allowed length'); + + if (chunk != null) { + push(chunks, chunk); + } + else { + msg.content = join('', chunks); + msg.content_length = len; + } + }; + } + + // Pump data... + while (true) { + let chunk = src(); + + sink(chunk); + + if (chunk == null) + break; + } + + return true; + } + + return false; +}; + +export function build_querystring(q) { + let s = []; + + for (let k, v in q) { + push(s, + length(s) ? '&' : '?', + _urlencode(k, ENCODE_IF_NEEDED | ENCODE_FULL) || k, + '=', + _urlencode(v, ENCODE_IF_NEEDED | ENCODE_FULL) || v + ); + } + + return join('', s); +}; + +export function urlencode(value) { + if (value == null) + return null; + + value = '' + value; + + return _urlencode(value, ENCODE_IF_NEEDED | ENCODE_FULL) || value; +}; + +export function urldecode(value, decode_plus) { + if (value == null) + return null; + + value = '' + value; + + return _urldecode(value, DECODE_IF_NEEDED | (decode_plus ? DECODE_PLUS : 0)) || value; +}; + +// Extract and split urlencoded data pairs, separated bei either "&" or ";" +// 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. +export function urldecode_params(url, tbl) { + let parser, name, value; + let params = tbl || {}; + + parser = urlencoded_parser(function(what, buffer, length) { + if (what == parser.TUPLE) { + name = null; + value = null; + } + else if (what == parser.NAME) { + name = _urldecode(buffer); + } + else if (what == parser.VALUE && name) { + params[name] = _urldecode(buffer) || ''; + } + + return true; + }); + + if (parser) { + let m = match(('' + (url || '')), /[^?]*$/); + + parser.parse(m ? m[0] : ''); + parser.parse(null); + } + + return params; +}; + +// Encode each key-value-pair in given table to x-www-urlencoded format, +// separated by '&'. Tables are encoded as parameters with multiple values by +// repeating the parameter name with each value. +export function urlencode_params(tbl) { + let enc = []; + + for (let k, v in tbl) { + if (type(v) == 'array') { + for (let v2 in v) { + if (length(enc)) + push(enc, '&'); + + push(enc, + _urlencode(k), + '=', + _urlencode('' + v2)); + } + } + else { + if (length(enc)) + push(enc, '&'); + + push(enc, + _urlencode(k), + '=', + _urlencode('' + v)); + } + } + + return join(enc, ''); +}; + + +// Default IO routines suitable for CGI invocation +let avail_len = +getenv('CONTENT_LENGTH'); + +const default_source = () => { + let rlen = min(avail_len, 4096); + + if (rlen == 0) { + stdin.close(); + + return null; + } + + let chunk = stdin.read(rlen); + + if (chunk == null) + die(`Input read error: ${fserror()}`); + + avail_len -= length(chunk); + + return chunk; +}; + +const default_sink = (...chunks) => { + for (let chunk in chunks) + stdout.write(chunk); + + stdout.flush(); +}; + +const Class = { + formvalue: function(name, noparse) { + if (!noparse && !this.parsed_input) + this._parse_input(); + + if (name != null) + return this.message.params[name]; + else + return this.message.params; + }, + + formvaluetable: function(prefix) { + let vals = {}; + + prefix = (prefix || '') + '.'; + + if (!this.parsed_input) + this._parse_input(); + + for (let k, v in this.message.params) + if (index(k, prefix) == 0) + vals[substr(k, length(prefix))] = '' + v; + + return vals; + }, + + content: function() { + if (!this.parsed_input) + this._parse_input(); + + return this.message.content; + }, + + getcookie: function(name) { + return header_attribute(`cookie; ${this.getenv('HTTP_COOKIE') ?? ''}`, name); + }, + + getenv: function(name) { + if (name != null) + return this.message.env[name]; + else + return this.message.env; + }, + + setfilehandler: function(callback) { + if (type(callback) == 'resource' && type(callback.call) == 'function') + this.filehandler = (...args) => callback.call(...args); + else if (type(callback) == 'function') + this.filehandler = callback; + else + die('Invalid callback argument for setfilehandler()'); + + if (!this.parsed_input) + return; + + // 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. + for (let name, value in this.message.params) { + while (value?.fd) { + let data = value.fd.read(1024); + let eof = (data == null || data == ''); + + callback(value, data, eof); + + if (eof) { + value.fd.close(); + value.fd = null; + } + } + } + }, + + _parse_input: function() { + parse_message_body( + this.input, + this.message, + this.filehandler + ); + + this.parsed_input = true; + }, + + close: function() { + this.write_headers(); + this.closed = true; + }, + + header: function(key, value) { + this.headers ??= {}; + this.headers[lc(key)] = value; + }, + + prepare_content: function(mime) { + if (!this.headers?.['content-type']) { + if (mime == 'application/xhtml+xml') { + if (index(this.getenv('HTTP_ACCEPT'), mime) == -1) { + mime = 'text/html; charset=UTF-8'; + this.header('Vary', 'Accept'); + } + } + + this.header('Content-Type', mime); + } + }, + + status: function(code, message) { + this.status_code = code ?? 200; + this.status_message = message ?? 'OK'; + }, + + write_headers: function() { + if (this.eoh) + return; + + if (!this.status_code) + this.status(); + + if (!this.headers?.['content-type']) + this.header('Content-Type', 'text/html; charset=UTF-8'); + + if (!this.headers?.['cache-control']) { + this.header('Cache-Control', 'no-cache'); + this.header('Expires', '0'); + } + + if (!this.headers?.['x-frame-options']) + this.header('X-Frame-Options', 'SAMEORIGIN'); + + if (!this.headers?.['x-xss-protection']) + this.header('X-XSS-Protection', '1; mode=block'); + + if (!this.headers?.['x-content-type-options']) + this.header('X-Content-Type-Options', 'nosniff'); + + this.output('Status: '); + this.output(this.status_code); + this.output(' '); + this.output(this.status_message); + this.output('\r\n'); + + for (let k, v in this.headers) { + this.output(k); + this.output(': '); + this.output(v); + this.output('\r\n'); + } + + this.output('\r\n'); + + this.eoh = true; + }, + + // If the content chunk is nil this function will automatically invoke close. + write: function(content) { + if (content != null) { + this.write_headers(); + this.output(content); + + return true; + } + else { + this.close(); + } + }, + + redirect: function(url) { + this.status(302, 'Found'); + this.header('Location', url ?? '/'); + this.close(); + }, + + write_json: function(value) { + this.write(sprintf('%.J', value)); + }, + + urlencode, + urlencode_params, + + urldecode, + urldecode_params, + + build_querystring +}; + +export default function(env, sourcein, sinkout) { + return proto({ + input: sourcein ?? default_source, + output: sinkout ?? default_sink, + + // File handler nil by default to let .content() work + file: null, + + // HTTP-Message table + message: { + env, + headers: {}, + params: urldecode_params(env?.QUERY_STRING ?? '') + }, + + parsed_input: false + }, Class); +}; diff --git a/modules/luci-base/ucode/runtime.uc b/modules/luci-base/ucode/runtime.uc new file mode 100644 index 0000000000..a8b6812e74 --- /dev/null +++ b/modules/luci-base/ucode/runtime.uc @@ -0,0 +1,162 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { access, basename } from 'fs'; +import { cursor } from 'uci'; + +const template_directory = '/usr/share/ucode/luci/template'; + +function cut_message(msg) { + return trim(replace(msg, /\n--\n.*$/, '')); +} + +function format_nested_exception(ex) { + let msg = replace(cut_message(ex.message), /(\n+( \|[^\n]*(\n|$))+)/, (m, m1) => { + m1 = replace(m1, /(^|\n) \| ?/g, '$1'); + m = match(m1, /^(.+?)\n(In.*line \d+, byte \d+:.+)$/); + + return ` + <div class="exception"> + <div class="message">${cut_message(m ? m[1] : m1)}</div> + ${m ? `<pre class="context">${trim(m[2])}</pre>` : ''} + </div> + `; + }); + + return ` + <div class="exception"> + <div class="message">${cut_message(msg)}</div> + <pre class="context">${trim(ex.stacktrace[0].context)}</pre> + </div> + `; +} + +function format_lua_exception(ex) { + let m = match(ex.message, /^(.+)\nstack traceback:\n(.+)$/); + + return ` + <div class="exception"> + <div class="message">${cut_message(m ? m[1] : ex.message)}</div> + <pre class="context">${m ? trim(replace(m[2], /(^|\n)\t/g, '$1')) : ex.stacktrace[0].context}</pre> + </div> + `; +} + +const Class = { + init_lua: function() { + if (!this.L) { + this.L = this.env.dispatcher.load_luabridge().create(); + this.L.set('L', proto({ write: print }, this.env)); + this.L.invoke('require', 'luci.ucodebridge'); + + this.env.lua_active = true; + } + + return this.L; + }, + + render_ucode: function(path, scope) { + let tmplfunc = loadfile(path, { raw_mode: false }); + call(tmplfunc, null, scope ?? {}); + }, + + render_lua: function(path, scope) { + let vm = this.init_lua(); + let render = vm.get('_G', 'luci', 'ucodebridge', 'render'); + + render.call(path, scope ?? {}); + }, + + trycompile: function(path) { + let ucode_path = `${template_directory}/${path}.ut`; + + if (access(ucode_path)) { + try { + loadfile(ucode_path, { raw_mode: false }); + } + catch (ucode_err) { + return `Unable to compile '${path}' as ucode template: ${format_nested_exception(ucode_err)}`; + } + } + else { + try { + let vm = this.init_lua(); + let compile = vm.get('_G', 'luci', 'ucodebridge', 'compile'); + + compile.call(path); + } + catch (lua_err) { + return `Unable to compile '${path}' as Lua template: ${format_lua_exception(lua_err)}`; + } + } + + return true; + }, + + render_any: function(path, scope) { + let ucode_path = `${template_directory}/${path}.ut`; + + scope = proto(scope ?? {}, this.scopes[-1]); + + push(this.scopes, scope); + + try { + if (access(ucode_path)) + this.render_ucode(ucode_path, scope); + else + this.render_lua(path, scope); + } + catch (ex) { + pop(this.scopes); + die(ex); + } + + pop(this.scopes); + }, + + render: function(path, scope) { + let self = this; + this.env.http.write(render(() => self.render_any(path, scope))); + }, + + call: function(modname, method, ...args) { + let vm = this.init_lua(); + let lcall = vm.get('_G', 'luci', 'ucodebridge', 'call'); + + return lcall.call(modname, method, ...args); + } +}; + +export default function(env) { + const self = proto({ env: env ??= {}, scopes: [ proto(env, global) ], global }, Class); + const uci = cursor(); + + // determine theme + let media = uci.get('luci', 'main', 'mediaurlbase'); + let status = self.trycompile(`themes/${basename(media)}/header`); + + if (status !== true) { + media = null; + + for (let k, v in uci.get_all('luci', 'themes')) { + if (substr(k, 0, 1) != '.') { + status = self.trycompile(`themes/${basename(v)}/header`); + + if (status === true) { + media = v; + break; + } + } + } + + if (!media) + error500(`Unable to render any theme header template, last error was:\n${status}`); + } + + self.env.media = media; + self.env.theme = basename(media); + self.env.resource = uci.get('luci', 'main', 'resourcebase'); + self.env.include = (...args) => self.render_any(...args); + + return self; +}; diff --git a/modules/luci-base/ucode/sys.uc b/modules/luci-base/ucode/sys.uc new file mode 100644 index 0000000000..d4db91a9b9 --- /dev/null +++ b/modules/luci-base/ucode/sys.uc @@ -0,0 +1,157 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { basename, readlink, readfile, open, popen, stat, glob } from 'fs'; + +export function process_list() { + const top = popen('/bin/busybox top -bn1'); + let line, list = []; + + for (let line = top.read('line'); length(line); line = top.read('line')) { + let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][<NW ][<N ]) +([0-9]+m?) +([0-9]+%) +([0-9]+%) +(.+)$/); + + if (m && m[8] != '/bin/busybox top -bn1') { + push(list, { + PID: m[1], + PPID: m[2], + USER: trim(m[3]), + STAT: m[4], + VSZ: m[5], + '%MEM': m[6], + '%CPU': m[7], + COMMAND: m[8] + }); + } + } + + top.close(); + + return list; +}; + +export function conntrack_list(callback) { + const etcpr = open('/etc/protocols'); + const protos = {}; + + if (etcpr) { + for (let line = etcpr.read('line'); length(line); line = etcpr.read('line')) { + const m = match(line, /^([^# \t\n]+)\s+([0-9]+)\s+/); + + if (m) + protos[m[2]] = m[1]; + } + + etcpr.close(); + } + + const nfct = open('/proc/net/nf_conntrack', 'r'); + let connt; + + if (nfct) { + for (let line = nfct.read('line'); length(line); line = nfct.read('line')) { + let m = match(line, /^(ipv[46]) +([0-9]+) +\S+ +([0-9]+) +(.+)\n$/); + + if (!m) + continue; + + let fam = m[1]; + let l3 = m[2]; + let l4 = m[3]; + let tuples = m[4]; + let timeout = null; + + m = match(tuples, /^([0-9]+) (.+)$/); + + if (m) { + timeout = m[1]; + tuples = m[2]; + } + + if (index(tuples, 'TIME_WAIT ') === 0) + continue; + + let e = { + bytes: 0, + packets: 0, + layer3: fam, + layer4: protos[l4] ?? 'unknown', + timeout: +timeout + }; + + for (let kv in match(tuples, / (\w+)=(\S+)/g)) { + switch (kv[1]) { + case 'bytes': + case 'packets': + e[kv[1]] += +kv[2]; + break; + + case 'src': + case 'dst': + e[kv[1]] ??= arrtoip(iptoarr(kv[2])); + break; + + case 'sport': + case 'dport': + e[kv[1]] ??= +kv[2]; + break; + + default: + e[kv[1]] = kv[2]; + } + } + + if (callback) + callback(e); + else + push(connt ??= [], e); + } + + nfct.close(); + } + + return callback ? true : (connt ?? []); +}; + +export function init_list() { + return map(filter(glob('/etc/init.d/*'), path => { + const s = stat(path); + + return s?.type == 'file' && s?.perm?.user_exec; + }), basename); +}; + +export function init_index(name) { + const src = readfile(`/etc/init.d/${basename(name)}`, 1024); + const idx = []; + + for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) { + switch (m[1]) { + case 'START': idx[0] = +trim(m[2], '"\''); break; + case 'STOP': idx[1] = +trim(m[2], '"\''); break; + } + } + + return length(idx) ? idx : null; +}; + +export function init_enabled(name) { + for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) { + const ln = readlink(path); + const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`); + const s2 = stat(`/etc/init.d/${basename(name)}`); + + if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec) + return true; + } + + return false; +}; + +export function init_action(name, action) { + const s = stat(`/etc/init.d/${basename(name)}`); + + if (s?.type != 'file' || s?.user_exec == false) + return false; + + return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`); +}; diff --git a/modules/luci-base/luasrc/view/csrftoken.htm b/modules/luci-base/ucode/template/csrftoken.ut index 57ac03f3bf..4e96eebe90 100644 --- a/modules/luci-base/luasrc/view/csrftoken.htm +++ b/modules/luci-base/ucode/template/csrftoken.ut @@ -1,19 +1,19 @@ -<%# - Copyright 2015 Jo-Philipp Wich <jow@openwrt.org> +{# + Copyright 2015-2022 Jo-Philipp Wich <jo@mein.io> Licensed to the public under the Apache License 2.0. --%> +-#} -<%+header%> +{% include('header') %} -<h2 name="content"><%:Form token mismatch%></h2> +<h2 name="content">{{ _('Form token mismatch') }}</h2> <br /> -<p class="alert-message"><%:The submitted security token is invalid or already expired!%></p> +<p class="alert-message">{{ _('The submitted security token is invalid or already expired!') }}</p> -<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> +`) }}</p> <hr /> @@ -21,4 +21,4 @@ <strong><a href="#" onclick="window.history.back();">Continue »</a></strong> </p> -<%+footer%> +{% include('footer') %} diff --git a/modules/luci-base/ucode/template/error404.ut b/modules/luci-base/ucode/template/error404.ut new file mode 100644 index 0000000000..90c3d3784b --- /dev/null +++ b/modules/luci-base/ucode/template/error404.ut @@ -0,0 +1,14 @@ +{# + Copyright 2008 Steven Barth <steven@midlink.org> + Copyright 2008-2022 Jo-Philipp Wich <jo@mein.io> + Licensed to the public under the Apache License 2.0. +-#} + +{% include('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') }}: {{ dispatcher.build_url(...ctx.request_path) }}</tt> + +{% include('footer') %} diff --git a/modules/luci-base/ucode/template/error500.ut b/modules/luci-base/ucode/template/error500.ut new file mode 100644 index 0000000000..39a0eec678 --- /dev/null +++ b/modules/luci-base/ucode/template/error500.ut @@ -0,0 +1,67 @@ +{# + Copyright 2022 Jo-Philipp Wich <jo@mein.io> + Licensed to the public under the Apache License 2.0. +-#} + +<!--]]>--><!--'>--><!--">--> +<style type="text/css"> + body { + line-height: 1.5; + font-size: 14px; + font-family: sans-serif; + } + + .error500 * { + margin: 0; + padding: 0; + color: inherit; + } + + .error500 { + box-sizing: border-box; + position: fixed; + z-index: 999999; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + overflow: auto; + background: #ffe; + color: #f00 !important; + padding: 1em; + } + + .error500 h1 { + margin-bottom: .5em; + } + + .error500 .exception { + font-weight: normal; + white-space: normal; + margin: .25em; + padding: .5em; + border: 1px solid #f00; + background: rgba(204, 204, 204, .2); + } + + .error500 .message { + font-weight: bold; + white-space: pre-line; + } + + .error500 .context { + margin-top: 2em; + } +</style> + +<div class="error500"> + <h1>{{ title }}</h1> + <div class="message">{{ message }}</div> + + {% if (exception): %} + <div class="exception"> + <div class="message">{{ exception.message }}</div> + <pre class="context">{{ exception.stacktrace[0].context }}</pre> + </div> + {% endif %} +</div> diff --git a/modules/luci-base/ucode/template/footer.ut b/modules/luci-base/ucode/template/footer.ut new file mode 100644 index 0000000000..22d4f136f0 --- /dev/null +++ b/modules/luci-base/ucode/template/footer.ut @@ -0,0 +1,23 @@ +{# + Copyright 2022 Jo-Philipp Wich <jo@mein.io> + Licensed to the public under the Apache License 2.0. +-#} + +{% const rollback = dispatcher.rollback_pending() %} +{% if (rollback || trigger_apply || trigger_revert): %} + <script type="text/javascript"> + document.addEventListener("luci-loaded", function() { + {% if (trigger_apply): %} + L.ui.changes.apply(true); + {% elif (trigger_revert): %} + L.ui.changes.revert(); + {% else %} + L.ui.changes.confirm(true, Date.now() + {{rollback.remaining * 1000}}, {{sprintf('%J', rollback.token)}}); + {% endif %} + }); + </script> +{% endif %} + +{% include(`themes/${theme}/footer`) %} + +<!-- Lua compatibility mode active: {{ lua_active ? 'yes' : 'no' }} --> diff --git a/modules/luci-base/ucode/template/header.ut b/modules/luci-base/ucode/template/header.ut new file mode 100644 index 0000000000..fb61da5146 --- /dev/null +++ b/modules/luci-base/ucode/template/header.ut @@ -0,0 +1,32 @@ +{# + Copyright 2022 Jo-Philipp Wich <jo@mein.io> + Licensed to the public under the Apache License 2.0. +-#} + +{% + include(`themes/${theme}/header`); +-%} + +<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({{ { + media : media, + resource : resource, + scriptname : http.getenv("SCRIPT_NAME"), + pathinfo : http.getenv("PATH_INFO"), + documentroot : http.getenv("DOCUMENT_ROOT"), + requestpath : ctx.request_path, + dispatchpath : ctx.path, + pollinterval : +config.main.pollinterval || 5, + ubuspath : config.main.ubuspath || '/ubus/', + sessionid : ctx.authsession, + token : ctx.authtoken, + nodespec : node, + apply_rollback : max(+config.apply.rollback || 90, 90), + apply_holdoff : max(+config.apply.holdoff || 4, 1), + apply_timeout : max(+config.apply.timeout || 5, 1), + apply_display : max(+config.apply.display || 1.5, 1), + rollback_token : rollback_token + } }}); +</script> diff --git a/modules/luci-base/ucode/template/sysauth.ut b/modules/luci-base/ucode/template/sysauth.ut new file mode 100644 index 0000000000..0fe873d440 --- /dev/null +++ b/modules/luci-base/ucode/template/sysauth.ut @@ -0,0 +1,74 @@ +{# + 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. +-#} + +{% include('header') %} + +<form method="post"> + {% if (fuser): %} + <div class="alert-message warning"> + <p>{{ _('Invalid username and/or password! Please try again.') }}</p> + </div> + {% endif %} + + <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">{{ _('Username') }}</label> + <div class="cbi-value-field"> + <input class="cbi-input-text" type="text" name="luci_username" value="{{ entityencode(duser, true) }}" /> + </div> + </div> + <div class="cbi-value cbi-value-last"> + <label class="cbi-value-title">{{ _('Password') }}</label> + <div class="cbi-value-field"> + <input class="cbi-input-text" type="password" name="luci_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> + +{% + let https_ports = uci.get('uhttpd', 'main', 'listen_https') ?? []; + + https_ports = uniq(filter( + map( + (type(https_ports) == 'string') ? split(https_port, /\s+/) : https_ports, + e => +match(e, /\d+$/)?.[0] + ), + p => (p >= 0 && p <= 65535) + )); +%} + +<script type="text/javascript">//<![CDATA[ + var input = document.getElementsByName('luci_password')[0]; + + if (input) + input.focus(); + + if (document.location.protocol != 'https:') { + {{ https_ports }}.forEach(function(port) { + var url = 'https://' + window.location.hostname + ':' + port + window.location.pathname; + var img = new Image(); + + img.onload = function() { window.location = url }; + img.src = 'https://' + window.location.hostname + ':' + port + '{{ resource }}/icons/loading.gif?' + Math.random(); + + setTimeout(function() { img.src = '' }, 5000); + }); + } +//]]></script> + +{% include('footer') %} diff --git a/modules/luci-base/ucode/template/view.ut b/modules/luci-base/ucode/template/view.ut new file mode 100644 index 0000000000..11ac824290 --- /dev/null +++ b/modules/luci-base/ucode/template/view.ut @@ -0,0 +1,12 @@ +{% include('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> + +{% include('footer') %} diff --git a/modules/luci-base/ucode/uhttpd.uc b/modules/luci-base/ucode/uhttpd.uc new file mode 100644 index 0000000000..df1ecc7865 --- /dev/null +++ b/modules/luci-base/ucode/uhttpd.uc @@ -0,0 +1,12 @@ +{% + +import dispatch from 'luci.dispatcher'; +import request from 'luci.http'; + +global.handle_request = function(env) { + let req = request(env, uhttpd.recv, uhttpd.send); + + dispatch(req); + + req.close(); +}; diff --git a/modules/luci-base/ucode/zoneinfo.uc b/modules/luci-base/ucode/zoneinfo.uc new file mode 100644 index 0000000000..c5e588dd6a --- /dev/null +++ b/modules/luci-base/ucode/zoneinfo.uc @@ -0,0 +1,453 @@ +// Autogenerated by zoneinfo2ucode.pl + +export default { + 'Africa/Abidjan': 'GMT0', + 'Africa/Accra': 'GMT0', + 'Africa/Addis Ababa': 'EAT-3', + 'Africa/Algiers': 'CET-1', + 'Africa/Asmara': 'EAT-3', + 'Africa/Bamako': 'GMT0', + 'Africa/Bangui': 'WAT-1', + 'Africa/Banjul': 'GMT0', + 'Africa/Bissau': 'GMT0', + 'Africa/Blantyre': 'CAT-2', + 'Africa/Brazzaville': 'WAT-1', + 'Africa/Bujumbura': 'CAT-2', + 'Africa/Cairo': 'EET-2', + 'Africa/Casablanca': '<+01>-1', + 'Africa/Ceuta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Africa/Conakry': 'GMT0', + 'Africa/Dakar': 'GMT0', + 'Africa/Dar es Salaam': 'EAT-3', + 'Africa/Djibouti': 'EAT-3', + 'Africa/Douala': 'WAT-1', + 'Africa/El Aaiun': '<+01>-1', + 'Africa/Freetown': 'GMT0', + 'Africa/Gaborone': 'CAT-2', + 'Africa/Harare': 'CAT-2', + 'Africa/Johannesburg': 'SAST-2', + 'Africa/Juba': 'CAT-2', + 'Africa/Kampala': 'EAT-3', + 'Africa/Khartoum': 'CAT-2', + 'Africa/Kigali': 'CAT-2', + 'Africa/Kinshasa': 'WAT-1', + 'Africa/Lagos': 'WAT-1', + 'Africa/Libreville': 'WAT-1', + 'Africa/Lome': 'GMT0', + 'Africa/Luanda': 'WAT-1', + 'Africa/Lubumbashi': 'CAT-2', + 'Africa/Lusaka': 'CAT-2', + 'Africa/Malabo': 'WAT-1', + 'Africa/Maputo': 'CAT-2', + 'Africa/Maseru': 'SAST-2', + 'Africa/Mbabane': 'SAST-2', + 'Africa/Mogadishu': 'EAT-3', + 'Africa/Monrovia': 'GMT0', + 'Africa/Nairobi': 'EAT-3', + 'Africa/Ndjamena': 'WAT-1', + 'Africa/Niamey': 'WAT-1', + 'Africa/Nouakchott': 'GMT0', + 'Africa/Ouagadougou': 'GMT0', + 'Africa/Porto-Novo': 'WAT-1', + 'Africa/Sao Tome': 'GMT0', + 'Africa/Tripoli': 'EET-2', + 'Africa/Tunis': 'CET-1', + 'Africa/Windhoek': 'CAT-2', + 'America/Adak': 'HST10HDT,M3.2.0,M11.1.0', + 'America/Anchorage': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Anguilla': 'AST4', + 'America/Antigua': 'AST4', + 'America/Araguaina': '<-03>3', + 'America/Argentina/Buenos Aires': '<-03>3', + 'America/Argentina/Catamarca': '<-03>3', + 'America/Argentina/Cordoba': '<-03>3', + 'America/Argentina/Jujuy': '<-03>3', + 'America/Argentina/La Rioja': '<-03>3', + 'America/Argentina/Mendoza': '<-03>3', + 'America/Argentina/Rio Gallegos': '<-03>3', + 'America/Argentina/Salta': '<-03>3', + 'America/Argentina/San Juan': '<-03>3', + 'America/Argentina/San Luis': '<-03>3', + 'America/Argentina/Tucuman': '<-03>3', + 'America/Argentina/Ushuaia': '<-03>3', + 'America/Aruba': 'AST4', + 'America/Asuncion': '<-04>4<-03>,M10.1.0/0,M3.4.0/0', + 'America/Atikokan': 'EST5', + 'America/Bahia': '<-03>3', + 'America/Bahia Banderas': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Barbados': 'AST4', + 'America/Belem': '<-03>3', + 'America/Belize': 'CST6', + 'America/Blanc-Sablon': 'AST4', + 'America/Boa Vista': '<-04>4', + 'America/Bogota': '<-05>5', + 'America/Boise': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Cambridge Bay': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Campo Grande': '<-04>4', + 'America/Cancun': 'EST5', + 'America/Caracas': '<-04>4', + 'America/Cayenne': '<-03>3', + 'America/Cayman': 'EST5', + 'America/Chicago': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Chihuahua': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Costa Rica': 'CST6', + 'America/Creston': 'MST7', + 'America/Cuiaba': '<-04>4', + 'America/Curacao': 'AST4', + 'America/Danmarkshavn': 'GMT0', + 'America/Dawson': 'MST7', + 'America/Dawson Creek': 'MST7', + 'America/Denver': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Detroit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Dominica': 'AST4', + 'America/Edmonton': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Eirunepe': '<-05>5', + 'America/El Salvador': 'CST6', + 'America/Fort Nelson': 'MST7', + 'America/Fortaleza': '<-03>3', + 'America/Glace Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Goose Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Grand Turk': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Grenada': 'AST4', + 'America/Guadeloupe': 'AST4', + 'America/Guatemala': 'CST6', + 'America/Guayaquil': '<-05>5', + 'America/Guyana': '<-04>4', + 'America/Halifax': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Havana': 'CST5CDT,M3.2.0/0,M11.1.0/1', + 'America/Hermosillo': 'MST7', + 'America/Indiana/Indianapolis': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Knox': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Marengo': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Petersburg': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Tell City': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Vevay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Vincennes': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Winamac': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Inuvik': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Iqaluit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Jamaica': 'EST5', + 'America/Juneau': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Kentucky/Louisville': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kentucky/Monticello': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kralendijk': 'AST4', + 'America/La Paz': '<-04>4', + 'America/Lima': '<-05>5', + 'America/Los Angeles': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Lower Princes': 'AST4', + 'America/Maceio': '<-03>3', + 'America/Managua': 'CST6', + 'America/Manaus': '<-04>4', + 'America/Marigot': 'AST4', + 'America/Martinique': 'AST4', + 'America/Matamoros': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Mazatlan': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Menominee': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Merida': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Metlakatla': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Mexico City': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Miquelon': '<-03>3<-02>,M3.2.0,M11.1.0', + 'America/Moncton': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Monterrey': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Montevideo': '<-03>3', + 'America/Montserrat': 'AST4', + 'America/Nassau': 'EST5EDT,M3.2.0,M11.1.0', + 'America/New York': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nipigon': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nome': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Noronha': '<-02>2', + 'America/North Dakota/Beulah': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North Dakota/Center': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North Dakota/New Salem': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Nuuk': '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1', + 'America/Ojinaga': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Panama': 'EST5', + 'America/Pangnirtung': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Paramaribo': '<-03>3', + 'America/Phoenix': 'MST7', + 'America/Port of Spain': 'AST4', + 'America/Port-au-Prince': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Porto Velho': '<-04>4', + 'America/Puerto Rico': 'AST4', + 'America/Punta Arenas': '<-03>3', + 'America/Rainy River': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rankin Inlet': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Recife': '<-03>3', + 'America/Regina': 'CST6', + 'America/Resolute': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rio Branco': '<-05>5', + 'America/Santarem': '<-03>3', + 'America/Santiago': '<-04>4<-03>,M9.1.6/24,M4.1.6/24', + 'America/Santo Domingo': 'AST4', + 'America/Sao Paulo': '<-03>3', + 'America/Scoresbysund': '<-01>1<+00>,M3.5.0/0,M10.5.0/1', + 'America/Sitka': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/St Barthelemy': 'AST4', + 'America/St Johns': 'NST3:30NDT,M3.2.0,M11.1.0', + 'America/St Kitts': 'AST4', + 'America/St Lucia': 'AST4', + 'America/St Thomas': 'AST4', + 'America/St Vincent': 'AST4', + 'America/Swift Current': 'CST6', + 'America/Tegucigalpa': 'CST6', + 'America/Thule': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Thunder Bay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tijuana': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Toronto': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tortola': 'AST4', + 'America/Vancouver': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Whitehorse': 'MST7', + 'America/Winnipeg': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Yakutat': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Yellowknife': 'MST7MDT,M3.2.0,M11.1.0', + 'Antarctica/Casey': '<+11>-11', + 'Antarctica/Davis': '<+07>-7', + 'Antarctica/DumontDUrville': '<+10>-10', + 'Antarctica/Macquarie': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Antarctica/Mawson': '<+05>-5', + 'Antarctica/McMurdo': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Antarctica/Palmer': '<-03>3', + 'Antarctica/Rothera': '<-03>3', + 'Antarctica/Syowa': '<+03>-3', + 'Antarctica/Troll': '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3', + 'Antarctica/Vostok': '<+06>-6', + 'Arctic/Longyearbyen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Asia/Aden': '<+03>-3', + 'Asia/Almaty': '<+06>-6', + 'Asia/Amman': '<+03>-3', + 'Asia/Anadyr': '<+12>-12', + 'Asia/Aqtau': '<+05>-5', + 'Asia/Aqtobe': '<+05>-5', + 'Asia/Ashgabat': '<+05>-5', + 'Asia/Atyrau': '<+05>-5', + 'Asia/Baghdad': '<+03>-3', + 'Asia/Bahrain': '<+03>-3', + 'Asia/Baku': '<+04>-4', + 'Asia/Bangkok': '<+07>-7', + 'Asia/Barnaul': '<+07>-7', + 'Asia/Beirut': 'EET-2EEST,M3.5.0/0,M10.5.0/0', + 'Asia/Bishkek': '<+06>-6', + 'Asia/Brunei': '<+08>-8', + 'Asia/Chita': '<+09>-9', + 'Asia/Choibalsan': '<+08>-8', + 'Asia/Colombo': '<+0530>-5:30', + 'Asia/Damascus': '<+03>-3', + 'Asia/Dhaka': '<+06>-6', + 'Asia/Dili': '<+09>-9', + 'Asia/Dubai': '<+04>-4', + 'Asia/Dushanbe': '<+05>-5', + 'Asia/Famagusta': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Gaza': 'EET-2EEST,M3.4.4/50,M10.4.4/50', + 'Asia/Hebron': 'EET-2EEST,M3.4.4/50,M10.4.4/50', + 'Asia/Ho Chi Minh': '<+07>-7', + 'Asia/Hong Kong': 'HKT-8', + 'Asia/Hovd': '<+07>-7', + 'Asia/Irkutsk': '<+08>-8', + 'Asia/Jakarta': 'WIB-7', + 'Asia/Jayapura': 'WIT-9', + 'Asia/Jerusalem': 'IST-2IDT,M3.4.4/26,M10.5.0', + 'Asia/Kabul': '<+0430>-4:30', + 'Asia/Kamchatka': '<+12>-12', + 'Asia/Karachi': 'PKT-5', + 'Asia/Kathmandu': '<+0545>-5:45', + 'Asia/Khandyga': '<+09>-9', + 'Asia/Kolkata': 'IST-5:30', + 'Asia/Krasnoyarsk': '<+07>-7', + 'Asia/Kuala Lumpur': '<+08>-8', + 'Asia/Kuching': '<+08>-8', + 'Asia/Kuwait': '<+03>-3', + 'Asia/Macau': 'CST-8', + 'Asia/Magadan': '<+11>-11', + 'Asia/Makassar': 'WITA-8', + 'Asia/Manila': 'PST-8', + 'Asia/Muscat': '<+04>-4', + 'Asia/Nicosia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Novokuznetsk': '<+07>-7', + 'Asia/Novosibirsk': '<+07>-7', + 'Asia/Omsk': '<+06>-6', + 'Asia/Oral': '<+05>-5', + 'Asia/Phnom Penh': '<+07>-7', + 'Asia/Pontianak': 'WIB-7', + 'Asia/Pyongyang': 'KST-9', + 'Asia/Qatar': '<+03>-3', + 'Asia/Qostanay': '<+06>-6', + 'Asia/Qyzylorda': '<+05>-5', + 'Asia/Riyadh': '<+03>-3', + 'Asia/Sakhalin': '<+11>-11', + 'Asia/Samarkand': '<+05>-5', + 'Asia/Seoul': 'KST-9', + 'Asia/Shanghai': 'CST-8', + 'Asia/Singapore': '<+08>-8', + 'Asia/Srednekolymsk': '<+11>-11', + 'Asia/Taipei': 'CST-8', + 'Asia/Tashkent': '<+05>-5', + 'Asia/Tbilisi': '<+04>-4', + 'Asia/Tehran': '<+0330>-3:30', + 'Asia/Thimphu': '<+06>-6', + 'Asia/Tokyo': 'JST-9', + 'Asia/Tomsk': '<+07>-7', + 'Asia/Ulaanbaatar': '<+08>-8', + 'Asia/Urumqi': '<+06>-6', + 'Asia/Ust-Nera': '<+10>-10', + 'Asia/Vientiane': '<+07>-7', + 'Asia/Vladivostok': '<+10>-10', + 'Asia/Yakutsk': '<+09>-9', + 'Asia/Yangon': '<+0630>-6:30', + 'Asia/Yekaterinburg': '<+05>-5', + 'Asia/Yerevan': '<+04>-4', + 'Atlantic/Azores': '<-01>1<+00>,M3.5.0/0,M10.5.0/1', + 'Atlantic/Bermuda': 'AST4ADT,M3.2.0,M11.1.0', + 'Atlantic/Canary': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Cape Verde': '<-01>1', + 'Atlantic/Faroe': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Madeira': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Reykjavik': 'GMT0', + 'Atlantic/South Georgia': '<-02>2', + 'Atlantic/St Helena': 'GMT0', + 'Atlantic/Stanley': '<-03>3', + 'Australia/Adelaide': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Brisbane': 'AEST-10', + 'Australia/Broken Hill': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Darwin': 'ACST-9:30', + 'Australia/Eucla': '<+0845>-8:45', + 'Australia/Hobart': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Lindeman': 'AEST-10', + 'Australia/Lord Howe': '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0', + 'Australia/Melbourne': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Perth': 'AWST-8', + 'Australia/Sydney': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Etc/GMT': 'GMT0', + 'Etc/GMT+1': '<-01>1', + 'Etc/GMT+10': '<-10>10', + 'Etc/GMT+11': '<-11>11', + 'Etc/GMT+12': '<-12>12', + 'Etc/GMT+2': '<-02>2', + 'Etc/GMT+3': '<-03>3', + 'Etc/GMT+4': '<-04>4', + 'Etc/GMT+5': '<-05>5', + 'Etc/GMT+6': '<-06>6', + 'Etc/GMT+7': '<-07>7', + 'Etc/GMT+8': '<-08>8', + 'Etc/GMT+9': '<-09>9', + 'Etc/GMT-1': '<+01>-1', + 'Etc/GMT-10': '<+10>-10', + 'Etc/GMT-11': '<+11>-11', + 'Etc/GMT-12': '<+12>-12', + 'Etc/GMT-13': '<+13>-13', + 'Etc/GMT-14': '<+14>-14', + 'Etc/GMT-2': '<+02>-2', + 'Etc/GMT-3': '<+03>-3', + 'Etc/GMT-4': '<+04>-4', + 'Etc/GMT-5': '<+05>-5', + 'Etc/GMT-6': '<+06>-6', + 'Etc/GMT-7': '<+07>-7', + 'Etc/GMT-8': '<+08>-8', + 'Etc/GMT-9': '<+09>-9', + 'Europe/Amsterdam': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Andorra': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Astrakhan': '<+04>-4', + 'Europe/Athens': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Belgrade': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Berlin': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bratislava': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Brussels': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bucharest': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Budapest': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Busingen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Chisinau': 'EET-2EEST,M3.5.0,M10.5.0/3', + 'Europe/Copenhagen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Dublin': 'IST-1GMT0,M10.5.0,M3.5.0/1', + 'Europe/Gibraltar': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Guernsey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Helsinki': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Isle of Man': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Istanbul': '<+03>-3', + 'Europe/Jersey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Kaliningrad': 'EET-2', + 'Europe/Kirov': '<+03>-3', + 'Europe/Kyiv': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Lisbon': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Europe/Ljubljana': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/London': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Luxembourg': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Madrid': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Malta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Mariehamn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Minsk': '<+03>-3', + 'Europe/Monaco': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Moscow': 'MSK-3', + 'Europe/Oslo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Paris': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Podgorica': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Prague': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Riga': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Rome': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Samara': '<+04>-4', + 'Europe/San Marino': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sarajevo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Saratov': '<+04>-4', + 'Europe/Simferopol': 'MSK-3', + 'Europe/Skopje': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sofia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Stockholm': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Tallinn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Tirane': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Ulyanovsk': '<+04>-4', + 'Europe/Vaduz': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vatican': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vienna': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vilnius': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Volgograd': '<+03>-3', + 'Europe/Warsaw': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zagreb': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zurich': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Indian/Antananarivo': 'EAT-3', + 'Indian/Chagos': '<+06>-6', + 'Indian/Christmas': '<+07>-7', + 'Indian/Cocos': '<+0630>-6:30', + 'Indian/Comoro': 'EAT-3', + 'Indian/Kerguelen': '<+05>-5', + 'Indian/Mahe': '<+04>-4', + 'Indian/Maldives': '<+05>-5', + 'Indian/Mauritius': '<+04>-4', + 'Indian/Mayotte': 'EAT-3', + 'Indian/Reunion': '<+04>-4', + 'Pacific/Apia': '<+13>-13', + 'Pacific/Auckland': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Pacific/Bougainville': '<+11>-11', + 'Pacific/Chatham': '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45', + 'Pacific/Chuuk': '<+10>-10', + 'Pacific/Easter': '<-06>6<-05>,M9.1.6/22,M4.1.6/22', + 'Pacific/Efate': '<+11>-11', + 'Pacific/Fakaofo': '<+13>-13', + 'Pacific/Fiji': '<+12>-12<+13>,M11.2.0,M1.2.3/99', + 'Pacific/Funafuti': '<+12>-12', + 'Pacific/Galapagos': '<-06>6', + 'Pacific/Gambier': '<-09>9', + 'Pacific/Guadalcanal': '<+11>-11', + 'Pacific/Guam': 'ChST-10', + 'Pacific/Honolulu': 'HST10', + 'Pacific/Kanton': '<+13>-13', + 'Pacific/Kiritimati': '<+14>-14', + 'Pacific/Kosrae': '<+11>-11', + 'Pacific/Kwajalein': '<+12>-12', + 'Pacific/Majuro': '<+12>-12', + 'Pacific/Marquesas': '<-0930>9:30', + 'Pacific/Midway': 'SST11', + 'Pacific/Nauru': '<+12>-12', + 'Pacific/Niue': '<-11>11', + 'Pacific/Norfolk': '<+11>-11<+12>,M10.1.0,M4.1.0/3', + 'Pacific/Noumea': '<+11>-11', + 'Pacific/Pago Pago': 'SST11', + 'Pacific/Palau': '<+09>-9', + 'Pacific/Pitcairn': '<-08>8', + 'Pacific/Pohnpei': '<+11>-11', + 'Pacific/Port Moresby': '<+10>-10', + 'Pacific/Rarotonga': '<-10>10', + 'Pacific/Saipan': 'ChST-10', + 'Pacific/Tahiti': '<-10>10', + 'Pacific/Tarawa': '<+12>-12', + 'Pacific/Tongatapu': '<+13>-13', + 'Pacific/Wake': '<+12>-12', + 'Pacific/Wallis': '<+12>-12', +}; |