1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-02-14 11:31:51 +00:00

Add macvlan support

This commit is contained in:
Ycarus 2018-01-23 15:36:03 +01:00
parent 40fdd921b9
commit 9f8643647e
255 changed files with 134998 additions and 0 deletions

48
luci-base/Makefile Normal file
View file

@ -0,0 +1,48 @@
#
# Copyright (C) 2008-2015 The LuCI Team <luci@lists.subsignal.org>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-base
LUCI_TYPE:=mod
LUCI_BASENAME:=base
LUCI_TITLE:=LuCI core libraries
LUCI_DEPENDS:=+lua +libuci-lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc
PKG_SOURCE:=LuaSrcDiet-0.12.1.tar.bz2
PKG_SOURCE_URL:=https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/luasrcdiet
PKG_MD5SUM:=ed7680f2896269ae8633756e7edcf09050812f78c8f49e280e63c30d14f35aea
HOST_BUILD_DIR:=$(BUILD_DIR_HOST)/LuaSrcDiet-0.12.1
include $(INCLUDE_DIR)/host-build.mk
define Package/luci-base/conffiles
/etc/luci-uploads
/etc/config/luci
endef
include ../luci/luci.mk
define Host/Configure
endef
define Host/Compile
$(MAKE) -C src/ clean po2lmo
$(MAKE) -C $(HOST_BUILD_DIR) bin/LuaSrcDiet.lua
endef
define Host/Install
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) src/po2lmo $(1)/bin/po2lmo
$(INSTALL_BIN) $(HOST_BUILD_DIR)/bin/LuaSrcDiet.lua $(1)/bin/LuaSrcDiet
endef
$(eval $(call HostBuild))
# call BuildPackage - OpenWrt buildroot signature

5
luci-base/htdocs/cgi-bin/luci Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/lua
require "luci.cacheloader"
require "luci.sgi.cgi"
luci.dispatcher.indexcache = "/tmp/luci-indexcache"
luci.sgi.cgi.run()

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

View file

@ -0,0 +1,241 @@
/*
* xhr.js - XMLHttpRequest helper class
* (c) 2008-2010 Jo-Philipp Wich
*/
XHR = function()
{
this.reinit = function()
{
if (window.XMLHttpRequest) {
this._xmlHttp = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
this._xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else {
alert("xhr.js: XMLHttpRequest is not supported by this browser!");
}
}
this.busy = function() {
if (!this._xmlHttp)
return false;
switch (this._xmlHttp.readyState)
{
case 1:
case 2:
case 3:
return true;
default:
return false;
}
}
this.abort = function() {
if (this.busy())
this._xmlHttp.abort();
}
this.get = function(url,data,callback)
{
this.reinit();
var xhr = this._xmlHttp;
var code = this._encode(data);
url = location.protocol + '//' + location.host + url;
if (code)
if (url.substr(url.length-1,1) == '&')
url += code;
else
url += '?' + code;
xhr.open('GET', url, true);
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4) {
var json = null;
if (xhr.getResponseHeader("Content-Type") == "application/json") {
try {
json = eval('(' + xhr.responseText + ')');
}
catch(e) {
json = null;
}
}
callback(xhr, json);
}
}
xhr.send(null);
}
this.post = function(url,data,callback)
{
this.reinit();
var xhr = this._xmlHttp;
var code = this._encode(data);
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
callback(xhr);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('Content-length', code.length);
xhr.setRequestHeader('Connection', 'close');
xhr.send(code);
}
this.cancel = function()
{
this._xmlHttp.onreadystatechange = function(){};
this._xmlHttp.abort();
}
this.send_form = function(form,callback,extra_values)
{
var code = '';
for (var i = 0; i < form.elements.length; i++)
{
var e = form.elements[i];
if (e.options)
{
code += (code ? '&' : '') +
form.elements[i].name + '=' + encodeURIComponent(
e.options[e.selectedIndex].value
);
}
else if (e.length)
{
for (var j = 0; j < e.length; j++)
if (e[j].name) {
code += (code ? '&' : '') +
e[j].name + '=' + encodeURIComponent(e[j].value);
}
}
else
{
code += (code ? '&' : '') +
e.name + '=' + encodeURIComponent(e.value);
}
}
if (typeof extra_values == 'object')
for (var key in extra_values)
code += (code ? '&' : '') +
key + '=' + encodeURIComponent(extra_values[key]);
return(
(form.method == 'get')
? this.get(form.getAttribute('action'), code, callback)
: this.post(form.getAttribute('action'), code, callback)
);
}
this._encode = function(obj)
{
obj = obj ? obj : { };
obj['_'] = Math.random();
if (typeof obj == 'object')
{
var code = '';
var self = this;
for (var k in obj)
code += (code ? '&' : '') +
k + '=' + encodeURIComponent(obj[k]);
return code;
}
return obj;
}
}
XHR.get = function(url, data, callback)
{
(new XHR()).get(url, data, callback);
}
XHR.poll = function(interval, url, data, callback)
{
if (isNaN(interval) || interval < 1)
interval = 5;
if (!XHR._q)
{
XHR._t = 0;
XHR._q = [ ];
XHR._r = function() {
for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i])
{
if (!(XHR._t % e.interval) && !e.xhr.busy())
e.xhr.get(e.url, e.data, e.callback);
}
XHR._t++;
};
}
XHR._q.push({
interval: interval,
callback: callback,
url: url,
data: data,
xhr: new XHR()
});
XHR.run();
}
XHR.halt = function()
{
if (XHR._i)
{
/* show & set poll indicator */
try {
document.getElementById('xhr_poll_status').style.display = '';
document.getElementById('xhr_poll_status_on').style.display = 'none';
document.getElementById('xhr_poll_status_off').style.display = '';
} catch(e) { }
window.clearInterval(XHR._i);
XHR._i = null;
}
}
XHR.run = function()
{
if (XHR._r && !XHR._i)
{
/* show & set poll indicator */
try {
document.getElementById('xhr_poll_status').style.display = '';
document.getElementById('xhr_poll_status_on').style.display = '';
document.getElementById('xhr_poll_status_off').style.display = 'none';
} catch(e) { }
/* kick first round manually to prevent one second lag when setting up
* the poll interval */
XHR._r();
XHR._i = window.setInterval(XHR._r, 1000);
}
}
XHR.running = function()
{
return !!(XHR._r && XHR._i);
}

View file

@ -0,0 +1,12 @@
-- 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

1942
luci-base/luasrc/cbi.lua Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,471 @@
-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
local fs = require "nixio.fs"
local ip = require "luci.ip"
local math = require "math"
local util = require "luci.util"
local tonumber, tostring, type, unpack, select = tonumber, tostring, type, unpack, select
module "luci.cbi.datatypes"
_M['or'] = function(v, ...)
local i
for i = 1, select('#', ...), 2 do
local f = select(i, ...)
local a = select(i+1, ...)
if type(f) ~= "function" then
if f == v then
return true
end
i = i - 1
elseif f(v, unpack(a)) then
return true
end
end
return false
end
_M['and'] = function(v, ...)
local i
for i = 1, select('#', ...), 2 do
local f = select(i, ...)
local a = select(i+1, ...)
if type(f) ~= "function" then
if f ~= v then
return false
end
i = i - 1
elseif not f(v, unpack(a)) then
return false
end
end
return true
end
function neg(v, ...)
return _M['or'](v:gsub("^%s*!%s*", ""), ...)
end
function list(v, subvalidator, subargs)
if type(subvalidator) ~= "function" then
return false
end
local token
for token in v:gmatch("%S+") do
if not subvalidator(token, unpack(subargs)) then
return false
end
end
return true
end
function bool(val)
if val == "1" or val == "yes" or val == "on" or val == "true" then
return true
elseif val == "0" or val == "no" or val == "off" or val == "false" then
return true
elseif val == "" or val == nil then
return true
end
return false
end
function uinteger(val)
local n = tonumber(val)
if n ~= nil and math.floor(n) == n and n >= 0 then
return true
end
return false
end
function integer(val)
local n = tonumber(val)
if n ~= nil and math.floor(n) == n then
return true
end
return false
end
function ufloat(val)
local n = tonumber(val)
return ( n ~= nil and n >= 0 )
end
function float(val)
return ( tonumber(val) ~= nil )
end
function ipaddr(val)
return ip4addr(val) or ip6addr(val)
end
function ip4addr(val)
if val then
return ip.IPv4(val) and true or false
end
return false
end
function ip4prefix(val)
val = tonumber(val)
return ( val and val >= 0 and val <= 32 )
end
function ip6addr(val)
if val then
return ip.IPv6(val) and true or false
end
return false
end
function ip6prefix(val)
val = tonumber(val)
return ( val and val >= 0 and val <= 128 )
end
function ipmask(val)
return ipmask4(val) or ipmask6(val)
end
function ipmask4(val)
local ip, mask = val:match("^([^/]+)/([^/]+)$")
local bits = tonumber(mask)
if bits and (bits < 0 or bits > 32) then
return false
end
if not bits and mask and not ip4addr(mask) then
return false
end
return ip4addr(ip or val)
end
function ipmask6(val)
local ip, mask = val:match("^([^/]+)/([^/]+)$")
local bits = tonumber(mask)
if bits and (bits < 0 or bits > 128) then
return false
end
if not bits and mask and not ip6addr(mask) then
return false
end
return ip6addr(ip or val)
end
function ip6hostid(val)
if val and val:match("^[a-fA-F0-9:]+$") and (#val > 2) then
return (ip6addr("2001:db8:0:0" .. val) or ip6addr("2001:db8:0:0:" .. val))
end
return false
end
function port(val)
val = tonumber(val)
return ( val and val >= 0 and val <= 65535 )
end
function portrange(val)
local p1, p2 = val:match("^(%d+)%-(%d+)$")
if p1 and p2 and port(p1) and port(p2) then
return true
else
return port(val)
end
end
function macaddr(val)
if val and val:match(
"^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
"[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$"
) then
local parts = util.split( val, ":" )
for i = 1,6 do
parts[i] = tonumber( parts[i], 16 )
if parts[i] < 0 or parts[i] > 255 then
return false
end
end
return true
end
return false
end
function hostname(val)
if val and (#val < 254) and (
val:match("^[a-zA-Z_]+$") or
(val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
val:match("[^0-9%.]"))
) then
return true
end
return false
end
function host(val, ipv4only)
return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val))
end
function network(val)
return uciname(val) or host(val)
end
function hostport(val, ipv4only)
local h, p = val:match("^([^:]+):([^:]+)$")
return not not (h and p and host(h, ipv4only) and port(p))
end
function ip4addrport(val, bracket)
local h, p = val:match("^([^:]+):([^:]+)$")
return (h and p and ip4addr(h) and port(p))
end
function ip4addrport(val)
local h, p = val:match("^([^:]+):([^:]+)$")
return (h and p and ip4addr(h) and port(p))
end
function ipaddrport(val, bracket)
local h, p = val:match("^([^%[%]:]+):([^:]+)$")
if (h and p and ip4addr(h) and port(p)) then
return true
elseif (bracket == 1) then
h, p = val:match("^%[(.+)%]:([^:]+)$")
if (h and p and ip6addr(h) and port(p)) then
return true
end
end
h, p = val:match("^([^%[%]]+):([^:]+)$")
return (h and p and ip6addr(h) and port(p))
end
function wpakey(val)
if #val == 64 then
return (val:match("^[a-fA-F0-9]+$") ~= nil)
else
return (#val >= 8) and (#val <= 63)
end
end
function wepkey(val)
if val:sub(1, 2) == "s:" then
val = val:sub(3)
end
if (#val == 10) or (#val == 26) then
return (val:match("^[a-fA-F0-9]+$") ~= nil)
else
return (#val == 5) or (#val == 13)
end
end
function hexstring(val)
if val then
return (val:match("^[a-fA-F0-9]+$") ~= nil)
end
return false
end
function hex(val, maxbytes)
maxbytes = tonumber(maxbytes)
if val and maxbytes ~= nil then
return ((val:match("^0x[a-fA-F0-9]+$") ~= nil) and (#val <= 2 + maxbytes * 2))
end
return false
end
function base64(val)
if val then
return (val:match("^[a-zA-Z0-9/+]+=?=?$") ~= nil) and (math.fmod(#val, 4) == 0)
end
return false
end
function string(val)
return true -- Everything qualifies as valid string
end
function directory( val, seen )
local s = fs.stat(val)
seen = seen or { }
if s and not seen[s.ino] then
seen[s.ino] = true
if s.type == "dir" then
return true
elseif s.type == "lnk" then
return directory( fs.readlink(val), seen )
end
end
return false
end
function file( val, seen )
local s = fs.stat(val)
seen = seen or { }
if s and not seen[s.ino] then
seen[s.ino] = true
if s.type == "reg" then
return true
elseif s.type == "lnk" then
return file( fs.readlink(val), seen )
end
end
return false
end
function device( val, seen )
local s = fs.stat(val)
seen = seen or { }
if s and not seen[s.ino] then
seen[s.ino] = true
if s.type == "chr" or s.type == "blk" then
return true
elseif s.type == "lnk" then
return device( fs.readlink(val), seen )
end
end
return false
end
function uciname(val)
return (val:match("^[a-zA-Z0-9_]+$") ~= nil)
end
function range(val, min, max)
val = tonumber(val)
min = tonumber(min)
max = tonumber(max)
if val ~= nil and min ~= nil and max ~= nil then
return ((val >= min) and (val <= max))
end
return false
end
function min(val, min)
val = tonumber(val)
min = tonumber(min)
if val ~= nil and min ~= nil then
return (val >= min)
end
return false
end
function max(val, max)
val = tonumber(val)
max = tonumber(max)
if val ~= nil and max ~= nil then
return (val <= max)
end
return false
end
function rangelength(val, min, max)
val = tostring(val)
min = tonumber(min)
max = tonumber(max)
if val ~= nil and min ~= nil and max ~= nil then
return ((#val >= min) and (#val <= max))
end
return false
end
function minlength(val, min)
val = tostring(val)
min = tonumber(min)
if val ~= nil and min ~= nil then
return (#val >= min)
end
return false
end
function maxlength(val, max)
val = tostring(val)
max = tonumber(max)
if val ~= nil and max ~= nil then
return (#val <= max)
end
return false
end
function phonedigit(val)
return (val:match("^[0-9\*#!%.]+$") ~= nil)
end
function timehhmmss(val)
return (val:match("^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$") ~= nil)
end
function dateyyyymmdd(val)
if val ~= nil then
yearstr, monthstr, daystr = val:match("^(%d%d%d%d)-(%d%d)-(%d%d)$")
if (yearstr == nil) or (monthstr == nil) or (daystr == nil) then
return false;
end
year = tonumber(yearstr)
month = tonumber(monthstr)
day = tonumber(daystr)
if (year == nil) or (month == nil) or (day == nil) then
return false;
end
local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
local function is_leap_year(year)
return (year % 4 == 0) and ((year % 100 ~= 0) or (year % 400 == 0))
end
function get_days_in_month(month, year)
if (month == 2) and is_leap_year(year) then
return 29
else
return days_in_month[month]
end
end
if (year < 2015) then
return false
end
if ((month == 0) or (month > 12)) then
return false
end
if ((day == 0) or (day > get_days_in_month(month, year))) then
return false
end
return true
end
return false
end

View file

@ -0,0 +1,76 @@
-- 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

View file

@ -0,0 +1,18 @@
-- 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)

View file

@ -0,0 +1,49 @@
-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.controller.admin.servicectl", package.seeall)
function index()
entry({"servicectl"}, alias("servicectl", "status")).sysauth = "root"
entry({"servicectl", "status"}, call("action_status")).leaf = true
entry({"servicectl", "restart"}, post("action_restart")).leaf = true
end
function action_status()
local data = nixio.fs.readfile("/var/run/luci-reload-status")
if data then
luci.http.write("/etc/config/")
luci.http.write(data)
else
luci.http.write("finish")
end
end
function action_restart(args)
local uci = require "luci.model.uci".cursor()
if args then
local service
local services = { }
for service in args:gmatch("[%w_-]+") do
services[#services+1] = service
end
local command = uci:apply(services, true)
if nixio.fork() == 0 then
local i = nixio.open("/dev/null", "r")
local o = nixio.open("/dev/null", "w")
nixio.dup(i, nixio.stdin)
nixio.dup(o, nixio.stdout)
i:close()
o:close()
nixio.exec("/bin/sh", unpack(command))
else
luci.http.write("OK")
os.exit(0)
end
end
end

View file

@ -0,0 +1,37 @@
local debug = require "debug"
local io = require "io"
local collectgarbage, floor = collectgarbage, math.floor
module "luci.debug"
__file__ = debug.getinfo(1, 'S').source:sub(2)
-- Enables the memory tracer with given flags and returns a function to disable the tracer again
function trap_memtrace(flags, dest)
flags = flags or "clr"
local tracefile = io.open(dest or "/tmp/memtrace", "w")
local peak = 0
local function trap(what, line)
local info = debug.getinfo(2, "Sn")
local size = floor(collectgarbage("count"))
if size > peak then
peak = size
end
if tracefile then
tracefile:write(
"[", what, "] ", info.source, ":", (line or "?"), "\t",
(info.namewhat or ""), "\t",
(info.name or ""), "\t",
size, " (", peak, ")\n"
)
end
end
debug.sethook(trap, flags)
return function()
debug.sethook()
tracefile:close()
end
end

View file

@ -0,0 +1,918 @@
-- 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 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
authenticator = {}
-- Index table
local index = nil
-- Fastindex
local fi
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 node_visible(node)
if node then
return not (
(not node.title or #node.title == 0) or
(not node.target or node.hidden == true) or
(type(node.target) == "table" and node.target.type == "firstchild" and
(type(node.nodes) ~= "table" or not next(node.nodes)))
)
end
return false
end
function node_childs(node)
local rv = { }
if node then
local k, v
for k, v in util.spairs(node.nodes,
function(a, b)
return (node.nodes[a].order or 100)
< (node.nodes[b].order or 100)
end)
do
if node_visible(v) then
rv[#rv+1] = k
end
end
end
return rv
end
function error404(message)
http.status(404, "Not Found")
message = message or "Not Found"
require("luci.template")
if not util.copcall(luci.template.render, "error404") 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
function authenticator.htmlauth(validator, accs, default, template)
local user = http.formvalue("luci_username")
local pass = http.formvalue("luci_password")
if user and validator(user, pass) then
return user
end
require("luci.i18n")
require("luci.template")
context.path = {}
http.status(403, "Forbidden")
luci.template.render(template or "sysauth", {duser=default, fuser=user})
return false
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
for node in pathinfo:gmatch("[^/]+") do
r[#r+1] = node
end
local stat, err = util.coxpcall(function()
dispatch(context.request)
end, error500)
http.close()
--context._disable_memtrace()
end
local function require_post_security(target)
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 or request_val == ""))
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
function dispatch(request)
--context._disable_memtrace = require "luci.debug".trap_memtrace("l")
local ctx = context
ctx.path = request
local conf = require "luci.config"
assert(conf.main,
"/etc/config/luci seems to be corrupt, unable to find section 'main'")
local i18n = require "luci.i18n"
local lang = conf.main.lang or "auto"
if lang == "auto" then
local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
for lpat in aclang:gmatch("[%w-]+") do
lpat = lpat and lpat:gsub("-", "_")
if conf.languages[lpat] then
lang = lpat
break
end
end
end
if lang == "auto" then
lang = i18n.default
end
i18n.setlanguage(lang)
local c = ctx.tree
local stat
if not c then
c = createtree()
end
local track = {}
local args = {}
ctx.args = args
ctx.requestargs = ctx.requestargs or args
local n
local preq = {}
local freq = {}
for i, s in ipairs(request) do
preq[#preq+1] = s
freq[#freq+1] = s
c = c.nodes[s]
n = i
if not c then
break
end
util.update(track, c)
if c.leaf then
break
end
end
if c and c.leaf then
for j=n+1, #request do
args[#args+1] = request[j]
freq[#freq+1] = request[j]
end
end
ctx.requestpath = ctx.requestpath or freq
ctx.path = preq
if track.i18n then
i18n.loadc(track.i18n)
end
-- Init template engine
if (c and c.index) or not track.notemplate then
local tpl = require("luci.template")
local media = track.mediaurlbase or 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)
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
return string.format(
' %s="%s"', tostring(key),
util.pcdata(tostring( val
or (type(env[key]) ~= "function" and env[key])
or (scope and type(scope[key]) ~= "function" and scope[key])
or "" ))
)
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 = util.striptags;
pcdata = util.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(table, key)
if key == "controller" then
return build_url()
elseif key == "REQUEST_URI" then
return build_url(unpack(ctx.requestpath))
elseif key == "token" then
return ctx.authtoken
else
return rawget(table, key) or _G[key]
end
end})
end
track.dependent = (track.dependent ~= false)
assert(not track.dependent or not track.auto,
"Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
"has no parent node so the access to this location has been denied.\n" ..
"This is a software bug, please report this message at " ..
"https://github.com/openwrt/luci/issues"
)
if track.sysauth then
local authen = type(track.sysauth_authenticator) == "function"
and track.sysauth_authenticator
or authenticator[track.sysauth_authenticator]
local def = (type(track.sysauth) == "string") and track.sysauth
local accs = def and {track.sysauth} or track.sysauth
local sess = ctx.authsession
if not sess then
sess = http.getcookie("sysauth")
sess = sess and sess:match("^[a-f0-9]*$")
end
local sdat = (util.ubus("session", "get", { ubus_rpc_session = sess }) or { }).values
local user, token
if sdat then
user = sdat.user
token = sdat.token
else
local eu = http.getenv("HTTP_AUTH_USER")
local ep = http.getenv("HTTP_AUTH_PASS")
if eu and ep and sys.user.checkpasswd(eu, ep) then
authen = function() return eu end
end
end
if not util.contains(accs, user) then
if authen then
local user, sess = authen(sys.user.checkpasswd, accs, def, track.sysauth_template)
local token
if not user or not util.contains(accs, user) then
return
else
if not sess then
local sdat = util.ubus("session", "create", { timeout = tonumber(luci.config.sauth.sessiontime) })
if sdat then
token = sys.uniqueid(16)
util.ubus("session", "set", {
ubus_rpc_session = sdat.ubus_rpc_session,
values = {
user = user,
token = token,
section = sys.uniqueid(16)
}
})
sess = sdat.ubus_rpc_session
end
end
if sess and token then
http.header("Set-Cookie", 'sysauth=%s; path=%s' %{ sess, build_url() })
ctx.authsession = sess
ctx.authtoken = token
ctx.authuser = user
http.redirect(build_url(unpack(ctx.requestpath)))
end
end
else
http.status(403, "Forbidden")
return
end
else
ctx.authsession = sess
ctx.authtoken = token
ctx.authuser = user
end
end
if c and require_post_security(c.target) then
if not test_post_security(c) then
return
end
end
if track.setgroup then
sys.process.setgroup(track.setgroup)
end
if track.setuser then
sys.process.setuser(track.setuser)
end
local target = nil
if c then
if type(c.target) == "function" then
target = c.target
elseif type(c.target) == "table" then
target = c.target.target
end
end
if c and (c.index or type(target) == "function") then
ctx.dispatched = c
ctx.requested = ctx.requested or ctx.dispatched
end
if c and c.index then
local tpl = require "luci.template"
if util.copcall(tpl.render, "indexer", {}) then
return true
end
end
if type(target) == "function" then
util.copcall(function()
local oldenv = getfenv(target)
local module = require(c.module)
local env = setmetatable({}, {__index=
function(tbl, key)
return rawget(tbl, key) or module[key] or oldenv[key]
end})
setfenv(target, env)
end)
local ok, err
if type(c.target) == "table" then
ok, err = util.copcall(target, c.target, unpack(args))
else
ok, err = util.copcall(target, unpack(args))
end
assert(ok,
"Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
" dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
else
local root = node()
if not root or not root.target 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(request, "/") .. "'.\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
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
if indexcache then
local cachedate = fs.stat(indexcache, "mtime")
if cachedate then
local realdate = 0
for _, obj in ipairs(controllers) do
local omtime = fs.stat(obj, "mtime")
realdate = (omtime and omtime > realdate) and omtime or realdate
end
if cachedate > realdate and sys.process.info("uid") == 0 then
assert(
sys.process.info("uid") == fs.stat(indexcache, "uid")
and fs.stat(indexcache, "modestr") == "rw-------",
"Fatal: Indexcache is not sane!"
)
index = loadfile(indexcache)()
return index
end
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
assert(type(idx) == "function",
"Invalid controller file found\n" ..
"The file '" .. path .. "' contains no index() function.\n" ..
"Please make sure that the controller contains a valid " ..
"index function and verify the spelling!")
index[modname] = idx
end
if indexcache then
local f = nixio.open(indexcache, "w", 600)
f:writeall(util.get_bytecode(index))
f:close()
end
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}
local modi = {}
ctx.treecache = setmetatable({}, {__mode="v"})
ctx.tree = tree
ctx.modifiers = modi
-- Load default translation
require "luci.i18n".loadc("base")
local scope = setmetatable({}, {__index = luci.dispatcher})
for k, v in pairs(index) do
scope._NAME = k
setfenv(v, scope)
v()
end
local function modisort(a,b)
return modi[a].order < modi[b].order
end
for _, v in util.spairs(modi, modisort) do
scope._NAME = v.module
setfenv(v.func, scope)
v.func()
end
return tree
end
function modifier(func, order)
context.modifiers[#context.modifiers+1] = {
func = func,
order = order or 0,
module
= getfenv(2)._NAME
}
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 _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}
-- the node is "in request" if the request path matches
-- at least up to the length of the node path
if parent.inreq and context.path[#path+1] == last then
c.inreq = true
end
parent.nodes[last] = c
context.treecache[name] = c
end
return c
end
-- Subdispatchers --
function _firstchild()
local path = { unpack(context.path) }
local name = table.concat(path, ".")
local node = context.treecache[name]
local lowest
if node and node.nodes and next(node.nodes) then
local k, v
for k, v in pairs(node.nodes) do
if not lowest or
(v.order or 100) < (node.nodes[lowest].order or 100)
then
lowest = k
end
end
end
assert(lowest ~= nil,
"The requested node contains no childs, unable to redispatch")
path[#path+1] = lowest
dispatch(path)
end
function firstchild()
return { type = "firstchild", target = _firstchild }
end
function alias(...)
local req = {...}
return function(...)
for _, r in ipairs({...}) do
req[#req+1] = r
end
dispatch(req)
end
end
function rewrite(n, ...)
local req = {...}
return function(...)
local dispatched = util.clone(context.dispatched)
for i=1,n do
table.remove(dispatched, 1)
end
for i, r in ipairs(req) do
table.insert(dispatched, i, r)
end
for _, r in ipairs({...}) do
dispatched[#dispatched+1] = r
end
dispatch(dispatched)
end
end
local function _call(self, ...)
local func = getfenv()[self.name]
assert(func ~= nil,
'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?')
assert(type(func) == "function",
'The symbol "' .. self.name .. '" does not refer to a function but data ' ..
'of type "' .. type(func) .. '".')
if #self.argv > 0 then
return func(unpack(self.argv), ...)
else
return func(...)
end
end
function call(name, ...)
return {type = "call", argv = {...}, name = name, target = _call}
end
function post_on(params, name, ...)
return {
type = "call",
post = params,
argv = { ... },
name = name,
target = _call
}
end
function post(...)
return post_on(true, ...)
end
local _template = function(self, ...)
require "luci.template".render(self.view)
end
function template(name)
return {type = "template", view = name, target = _template}
end
local function _cbi(self, ...)
local cbi = require "luci.cbi"
local tpl = require "luci.template"
local http = require "luci.http"
local config = self.config or {}
local maps = cbi.load(self.model, ...)
local state = nil
for i, res in ipairs(maps) do
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 = { }
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
res:render({
firstmap = (i == 1),
applymap = applymap,
redirect = redirect,
messages = messages,
pageaction = pageaction,
parsechain = parsechain
})
end
if not config.nofooter then
tpl.render("cbi/footer", {
flow = config,
pageaction = pageaction,
redirect = redirect,
state = state,
autoapply = config.autoapply
})
end
end
function cbi(model, config)
return {
type = "cbi",
post = { ["cbi.submit"] = "1" },
config = config,
model = model,
target = _cbi
}
end
local function _arcombine(self, ...)
local argv = {...}
local target = #argv > 0 and self.targets[2] or self.targets[1]
setfenv(target.target, self.env)
target:target(unpack(argv))
end
function arcombine(trg1, trg2)
return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
end
local 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
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 = "cbi",
post = { ["cbi.submit"] = "1" },
model = model,
target = _form
}
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

View file

@ -0,0 +1,220 @@
---[[
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 childs 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
]]
---[[
Register a tree modifier.
@class function
@name modifier
@param func Modifier function
@param order Modifier order value (optional)
]]
---[[
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
]]
---[[
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 _
]]

268
luci-base/luasrc/http.lua Normal file
View file

@ -0,0 +1,268 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local ltn12 = require "luci.ltn12"
local protocol = require "luci.http.protocol"
local util = require "luci.util"
local string = require "string"
local coroutine = require "coroutine"
local table = require "table"
local ipairs, pairs, next, type, tostring, error =
ipairs, pairs, next, type, tostring, error
module "luci.http"
context = util.threadlocal()
Request = util.class()
function Request.__init__(self, env, sourcein, sinkerr)
self.input = sourcein
self.error = sinkerr
-- File handler nil by default to let .content() work
self.filehandler = nil
-- HTTP-Message table
self.message = {
env = env,
headers = {},
params = protocol.urldecode_params(env.QUERY_STRING or ""),
}
self.parsed_input = false
end
function Request.formvalue(self, name, noparse)
if not noparse and not self.parsed_input then
self:_parse_input()
end
if name then
return self.message.params[name]
else
return self.message.params
end
end
function Request.formvaluetable(self, prefix)
local vals = {}
prefix = prefix and prefix .. "." or "."
if not self.parsed_input then
self:_parse_input()
end
local void = self.message.params[nil]
for k, v in pairs(self.message.params) do
if k:find(prefix, 1, true) == 1 then
vals[k:sub(#prefix + 1)] = tostring(v)
end
end
return vals
end
function Request.content(self)
if not self.parsed_input then
self:_parse_input()
end
return self.message.content, self.message.content_length
end
function Request.getcookie(self, name)
local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
local p = ";" .. name .. "=(.-);"
local i, j, value = c:find(p)
return value and urldecode(value)
end
function Request.getenv(self, name)
if name then
return self.message.env[name]
else
return self.message.env
end
end
function Request.setfilehandler(self, callback)
self.filehandler = callback
-- If input has already been parsed then any files are either in temporary files
-- or are in self.message.params[key]
if self.parsed_input then
for param, value in pairs(self.message.params) do
repeat
-- We're only interested in files
if (not value["file"]) then break end
-- If we were able to write to temporary file
if (value["fd"]) then
fd = value["fd"]
local eof = false
repeat
filedata = fd:read(1024)
if (filedata:len() < 1024) then
eof = true
end
callback({ name=value["name"], file=value["file"] }, filedata, eof)
until (eof)
fd:close()
value["fd"] = nil
-- We had to read into memory
else
-- There should only be one numbered value in table - the data
for k, v in ipairs(value) do
callback({ name=value["name"], file=value["file"] }, v, true)
end
end
until true
end
end
end
function Request._parse_input(self)
protocol.parse_message_body(
self.input,
self.message,
self.filehandler
)
self.parsed_input = true
end
function close()
if not context.eoh then
context.eoh = true
coroutine.yield(3)
end
if not context.closed then
context.closed = true
coroutine.yield(5)
end
end
function content()
return context.request:content()
end
function formvalue(name, noparse)
return context.request:formvalue(name, noparse)
end
function formvaluetable(prefix)
return context.request:formvaluetable(prefix)
end
function getcookie(name)
return context.request:getcookie(name)
end
-- or the environment table itself.
function getenv(name)
return context.request:getenv(name)
end
function setfilehandler(callback)
return context.request:setfilehandler(callback)
end
function header(key, value)
if not context.headers then
context.headers = {}
end
context.headers[key:lower()] = value
coroutine.yield(2, key, value)
end
function prepare_content(mime)
if not context.headers or not context.headers["content-type"] then
if mime == "application/xhtml+xml" then
if not getenv("HTTP_ACCEPT") or
not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
mime = "text/html; charset=UTF-8"
end
header("Vary", "Accept")
end
header("Content-Type", mime)
end
end
function source()
return context.request.input
end
function status(code, message)
code = code or 200
message = message or "OK"
context.status = code
coroutine.yield(1, code, message)
end
-- This function is as a valid LTN12 sink.
-- If the content chunk is nil this function will automatically invoke close.
function write(content, src_err)
if not content then
if src_err then
error(src_err)
else
close()
end
return true
elseif #content == 0 then
return true
else
if not context.eoh then
if not context.status then
status()
end
if not context.headers or not context.headers["content-type"] then
header("Content-Type", "text/html; charset=utf-8")
end
if not context.headers["cache-control"] then
header("Cache-Control", "no-cache")
header("Expires", "0")
end
context.eoh = true
coroutine.yield(3)
end
coroutine.yield(4, content)
return true
end
end
function splice(fd, size)
coroutine.yield(6, fd, size)
end
function redirect(url)
if url == "" then url = "/" end
status(302, "Found")
header("Location", url)
close()
end
function build_querystring(q)
local s = { "?" }
for k, v in pairs(q) do
if #s > 1 then s[#s+1] = "&" end
s[#s+1] = urldecode(k)
s[#s+1] = "="
s[#s+1] = urldecode(v)
end
return table.concat(s, "")
end
urldecode = protocol.urldecode
urlencode = protocol.urlencode
function write_json(x)
util.serialize_json(x, write)
end

View file

@ -0,0 +1,165 @@
---[[
LuCI Web Framework high-level HTTP functions.
]]
module "luci.http"
---[[
Close the HTTP-Connection.
@class function
@name close
]]
---[[
Return the request content if the request was of unknown type.
@class function
@name content
@return HTTP request body
@return HTTP request body length
]]
---[[
Get a certain HTTP input value or a table of all input values.
@class function
@name formvalue
@param name Name of the GET or POST variable to fetch
@param noparse Don't parse POST data before getting the value
@return HTTP input value or table of all input value
]]
---[[
Get a table of all HTTP input values with a certain prefix.
@class function
@name formvaluetable
@param prefix Prefix
@return Table of all HTTP input values with given prefix
]]
---[[
Get the value of a certain HTTP-Cookie.
@class function
@name getcookie
@param name Cookie Name
@return String containing cookie data
]]
---[[
Get the value of a certain HTTP environment variable
or the environment table itself.
@class function
@name getenv
@param name Environment variable
@return HTTP environment value or environment table
]]
---[[
Set a handler function for incoming user file uploads.
@class function
@name setfilehandler
@param callback Handler function
]]
---[[
Send a HTTP-Header.
@class function
@name header
@param key Header key
@param value Header value
]]
---[[
Set the mime type of following content data.
@class function
@name prepare_content
@param mime Mimetype of following content
]]
---[[
Get the RAW HTTP input source
@class function
@name source
@return HTTP LTN12 source
]]
---[[
Set the HTTP status code and status message.
@class function
@name status
@param code Status code
@param message Status message
]]
---[[
Send a chunk of content data to the client.
This function is as a valid LTN12 sink.
If the content chunk is nil this function will automatically invoke close.
@class function
@name write
@param content Content chunk
@param src_err Error object from source (optional)
@see close
]]
---[[
Splice data from a filedescriptor to the client.
@class function
@name splice
@param fp File descriptor
@param size Bytes to splice (optional)
]]
---[[
Redirects the client to a new URL and closes the connection.
@class function
@name redirect
@param url Target URL
]]
---[[
Create a querystring out of a table of key - value pairs.
@class function
@name build_querystring
@param table Query string source table
@return Encoded HTTP query string
]]
---[[
Return the URL-decoded equivalent of a string.
@param str URL-encoded string
@param no_plus Don't decode + to " "
@return URL-decoded string
@see urlencode
]]
---[[
Return the URL-encoded equivalent of a string.
@param str Source string
@return URL-encoded string
@see urldecode
]]
---[[
Send the given data as JSON encoded string.
@class function
@name write_json
@param data Data to send
]]

View file

@ -0,0 +1,649 @@
-- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
-- This class contains several functions useful for http message- and content
-- decoding and to retrive form data from raw http messages.
module("luci.http.protocol", package.seeall)
local ltn12 = require("luci.ltn12")
HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size
-- the "+" sign to " " - and return the decoded string.
function urldecode( str, no_plus )
local function __chrdec( hex )
return string.char( tonumber( hex, 16 ) )
end
if type(str) == "string" then
if not no_plus then
str = str:gsub( "+", " " )
end
str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
end
return str
end
-- from given url or string. Returns a table with urldecoded values.
-- Simple parameters are stored as string values associated with the parameter
-- name within the table. Parameters with multiple values are stored as array
-- containing the corresponding values.
function urldecode_params( url, tbl )
local params = tbl or { }
if url:find("?") then
url = url:gsub( "^.+%?([^?]+)", "%1" )
end
for pair in url:gmatch( "[^&;]+" ) do
-- find key and value
local key = urldecode( pair:match("^([^=]+)") )
local val = urldecode( pair:match("^[^=]+=(.+)$") )
-- store
if type(key) == "string" and key:len() > 0 then
if type(val) ~= "string" then val = "" end
if not params[key] then
params[key] = val
elseif type(params[key]) ~= "table" then
params[key] = { params[key], val }
else
table.insert( params[key], val )
end
end
end
return params
end
function urlencode( str )
local function __chrenc( chr )
return string.format(
"%%%02x", string.byte( chr )
)
end
if type(str) == "string" then
str = str:gsub(
"([^a-zA-Z0-9$_%-%.%~])",
__chrenc
)
end
return str
end
-- separated by "&". Tables are encoded as parameters with multiple values by
-- repeating the parameter name with each value.
function urlencode_params( tbl )
local enc = ""
for k, v in pairs(tbl) do
if type(v) == "table" then
for i, v2 in ipairs(v) do
enc = enc .. ( #enc > 0 and "&" or "" ) ..
urlencode(k) .. "=" .. urlencode(v2)
end
else
enc = enc .. ( #enc > 0 and "&" or "" ) ..
urlencode(k) .. "=" .. urlencode(v)
end
end
return enc
end
-- (Internal function)
-- Initialize given parameter and coerce string into table when the parameter
-- already exists.
local function __initval( tbl, key )
if tbl[key] == nil then
tbl[key] = ""
elseif type(tbl[key]) == "string" then
tbl[key] = { tbl[key], "" }
else
table.insert( tbl[key], "" )
end
end
-- (Internal function)
-- Initialize given file parameter.
local function __initfileval( tbl, key, filename, fd )
if tbl[key] == nil then
tbl[key] = { file=filename, fd=fd, name=key, "" }
else
table.insert( tbl[key], "" )
end
end
-- (Internal function)
-- Append given data to given parameter, either by extending the string value
-- or by appending it to the last string in the parameter's value table.
local function __appendval( tbl, key, chunk )
if type(tbl[key]) == "table" then
tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk
else
tbl[key] = tbl[key] .. chunk
end
end
-- (Internal function)
-- Finish the value of given parameter, either by transforming the string value
-- or - in the case of multi value parameters - the last element in the
-- associated values table.
local function __finishval( tbl, key, handler )
if handler then
if type(tbl[key]) == "table" then
tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] )
else
tbl[key] = handler( tbl[key] )
end
end
end
-- Table of our process states
local process_states = { }
-- Extract "magic", the first line of a http message.
-- Extracts the message type ("get", "post" or "response"), the requested uri
-- or the status code if the line descripes a http response.
process_states['magic'] = function( msg, chunk, err )
if chunk ~= nil then
-- ignore empty lines before request
if #chunk == 0 then
return true, nil
end
-- Is it a request?
local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
-- Yup, it is
if method then
msg.type = "request"
msg.request_method = method:lower()
msg.request_uri = uri
msg.http_version = tonumber( http_ver )
msg.headers = { }
-- We're done, next state is header parsing
return true, function( chunk )
return process_states['headers']( msg, chunk )
end
-- Is it a response?
else
local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
-- Is a response
if code then
msg.type = "response"
msg.status_code = code
msg.status_message = message
msg.http_version = tonumber( http_ver )
msg.headers = { }
-- We're done, next state is header parsing
return true, function( chunk )
return process_states['headers']( msg, chunk )
end
end
end
end
-- Can't handle it
return nil, "Invalid HTTP message magic"
end
-- Extract headers from given string.
process_states['headers'] = function( msg, chunk )
if chunk ~= nil then
-- Look for a valid header format
local hdr, val = chunk:match( "^([A-Za-z][A-Za-z0-9%-_]+): +(.+)$" )
if type(hdr) == "string" and hdr:len() > 0 and
type(val) == "string" and val:len() > 0
then
msg.headers[hdr] = val
-- Valid header line, proceed
return true, nil
elseif #chunk == 0 then
-- Empty line, we won't accept data anymore
return false, nil
else
-- Junk data
return nil, "Invalid HTTP header received"
end
else
return nil, "Unexpected EOF"
end
end
-- data line by line with the trailing \r\n stripped of.
function header_source( sock )
return ltn12.source.simplify( function()
local chunk, err, part = sock:receive("*l")
-- Line too long
if chunk == nil then
if err ~= "timeout" then
return nil, part
and "Line exceeds maximum allowed length"
or "Unexpected EOF"
else
return nil, err
end
-- Line ok
elseif chunk ~= nil then
-- Strip trailing CR
chunk = chunk:gsub("\r$","")
return chunk, nil
end
end )
end
-- Content-Type. Stores all extracted data associated with its parameter name
-- in the params table withing 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 feeded 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 wheather the current chunk is the last one (eof)
function mimedecode_message_body( src, msg, filecb )
if msg and msg.env.CONTENT_TYPE then
msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$")
end
if not msg.mime_boundary then
return nil, "Invalid Content-Type found"
end
local tlen = 0
local inhdr = false
local field = nil
local store = nil
local lchunk = nil
local function parse_headers( chunk, field )
local stat
repeat
chunk, stat = chunk:gsub(
"^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n",
function(k,v)
field.headers[k] = v
return ""
end
)
until stat == 0
chunk, stat = chunk:gsub("^\r\n","")
-- End of headers
if stat > 0 then
if field.headers["Content-Disposition"] then
if field.headers["Content-Disposition"]:match("^form%-data; ") then
field.name = field.headers["Content-Disposition"]:match('name="(.-)"')
field.file = field.headers["Content-Disposition"]:match('filename="(.+)"$')
end
end
if not field.headers["Content-Type"] then
field.headers["Content-Type"] = "text/plain"
end
if field.name and field.file and filecb then
__initval( msg.params, field.name )
__appendval( msg.params, field.name, field.file )
store = filecb
elseif field.name and field.file then
local nxf = require "nixio"
local fd = nxf.mkstemp(field.name)
__initfileval ( msg.params, field.name, field.file, fd )
if fd then
store = function(hdr, buf, eof)
fd:write(buf)
if (eof) then
fd:seek(0, "set")
end
end
else
store = function( hdr, buf, eof )
__appendval( msg.params, field.name, buf )
end
end
elseif field.name then
__initval( msg.params, field.name )
store = function( hdr, buf, eof )
__appendval( msg.params, field.name, buf )
end
else
store = nil
end
return chunk, true
end
return chunk, false
end
local function snk( chunk )
tlen = tlen + ( chunk and #chunk or 0 )
if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
return nil, "Message body size exceeds Content-Length"
end
if chunk and not lchunk then
lchunk = "\r\n" .. chunk
elseif lchunk then
local data = lchunk .. ( chunk or "" )
local spos, epos, found
repeat
spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
if not spos then
spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
end
if spos then
local predata = data:sub( 1, spos - 1 )
if inhdr then
predata, eof = parse_headers( predata, field )
if not eof then
return nil, "Invalid MIME section header"
elseif not field.name then
return nil, "Invalid Content-Disposition header"
end
end
if store then
store( field, predata, true )
end
field = { headers = { } }
found = found or true
data, eof = parse_headers( data:sub( epos + 1, #data ), field )
inhdr = not eof
end
until not spos
if found then
-- We found at least some boundary. Save
-- the unparsed remaining data for the
-- next chunk.
lchunk, data = data, nil
else
-- There was a complete chunk without a boundary. Parse it as headers or
-- append it as data, depending on our current state.
if inhdr then
lchunk, eof = parse_headers( data, field )
inhdr = not eof
else
-- We're inside data, so append the data. Note that we only append
-- lchunk, not all of data, since there is a chance that chunk
-- contains half a boundary. Assuming that each chunk is at least the
-- boundary in size, this should prevent problems
store( field, lchunk, false )
lchunk, chunk = chunk, nil
end
end
end
return true
end
return ltn12.pump.all( src, snk )
end
-- Content-Type. Stores all extracted data associated with its parameter name
-- in the params table withing the given message object. Multiple parameter
-- values are stored as tables, ordinary ones as strings.
function urldecode_message_body( src, msg )
local tlen = 0
local lchunk = nil
local function snk( chunk )
tlen = tlen + ( chunk and #chunk or 0 )
if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
return nil, "Message body size exceeds Content-Length"
elseif tlen > HTTP_MAX_CONTENT then
return nil, "Message body size exceeds maximum allowed length"
end
if not lchunk and chunk then
lchunk = chunk
elseif lchunk then
local data = lchunk .. ( chunk or "&" )
local spos, epos
repeat
spos, epos = data:find("^.-[;&]")
if spos then
local pair = data:sub( spos, epos - 1 )
local key = pair:match("^(.-)=")
local val = pair:match("=([^%s]*)%s*$")
if key and #key > 0 then
__initval( msg.params, key )
__appendval( msg.params, key, val )
__finishval( msg.params, key, urldecode )
end
data = data:sub( epos + 1, #data )
end
until not spos
lchunk = data
end
return true
end
return ltn12.pump.all( src, snk )
end
-- version, message headers and resulting CGI environment variables from the
-- given ltn12 source.
function parse_message_header( src )
local ok = true
local msg = { }
local sink = ltn12.sink.simplify(
function( chunk )
return process_states['magic']( msg, chunk )
end
)
-- Pump input data...
while ok do
-- get data
ok, err = ltn12.pump.step( src, sink )
-- error
if not ok and err then
return nil, err
-- eof
elseif not ok then
-- Process get parameters
if ( msg.request_method == "get" or msg.request_method == "post" ) and
msg.request_uri:match("?")
then
msg.params = urldecode_params( msg.request_uri )
else
msg.params = { }
end
-- Populate common environment variables
msg.env = {
CONTENT_LENGTH = msg.headers['Content-Length'];
CONTENT_TYPE = msg.headers['Content-Type'] or msg.headers['Content-type'];
REQUEST_METHOD = msg.request_method:upper();
REQUEST_URI = msg.request_uri;
SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
SCRIPT_FILENAME = ""; -- XXX implement me
SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version);
QUERY_STRING = msg.request_uri:match("?")
and msg.request_uri:gsub("^.+?","") or ""
}
-- Populate HTTP_* environment variables
for i, hdr in ipairs( {
'Accept',
'Accept-Charset',
'Accept-Encoding',
'Accept-Language',
'Connection',
'Cookie',
'Host',
'Referer',
'User-Agent',
} ) do
local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
local val = msg.headers[hdr]
msg.env[var] = val
end
end
end
return msg
end
-- This function will examine the Content-Type within the given message object
-- to select the appropriate content decoder.
-- Currently the application/x-www-urlencoded and application/form-data
-- mime types are supported. If the encountered content encoding can't be
-- handled then the whole message body will be stored unaltered as "content"
-- property within the given message object.
function parse_message_body( src, msg, filecb )
-- Is it multipart/mime ?
if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
msg.env.CONTENT_TYPE:match("^multipart/form%-data")
then
return mimedecode_message_body( src, msg, filecb )
-- Is it application/x-www-form-urlencoded ?
elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
msg.env.CONTENT_TYPE:match("^application/x%-www%-form%-urlencoded")
then
return urldecode_message_body( src, msg, filecb )
-- Unhandled encoding
-- If a file callback is given then feed it chunk by chunk, else
-- store whole buffer in message.content
else
local sink
-- If we have a file callback then feed it
if type(filecb) == "function" then
local meta = {
name = "raw",
encoding = msg.env.CONTENT_TYPE
}
sink = function( chunk )
if chunk then
return filecb(meta, chunk, false)
else
return filecb(meta, nil, true)
end
end
-- ... else append to .content
else
msg.content = ""
msg.content_length = 0
sink = function( chunk )
if chunk then
if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
msg.content = msg.content .. chunk
msg.content_length = msg.content_length + #chunk
return true
else
return nil, "POST data exceeds maximum allowed length"
end
end
return true
end
end
-- Pump data...
while true do
local ok, err = ltn12.pump.step( src, sink )
if not ok and err then
return nil, err
elseif not ok then -- eof
return true
end
end
return true
end
end
statusmsg = {
[200] = "OK",
[206] = "Partial Content",
[301] = "Moved Permanently",
[302] = "Found",
[304] = "Not Modified",
[400] = "Bad Request",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[408] = "Request Time-out",
[411] = "Length Required",
[412] = "Precondition Failed",
[416] = "Requested range not satisfiable",
[500] = "Internal Server Error",
[503] = "Server Unavailable",
}

View file

@ -0,0 +1,142 @@
---[[
LuCI http protocol class.
This class contains several functions useful for http message- and content
decoding and to retrive form data from raw http messages.
]]
module "luci.http.protocol"
---[[
Decode an urlencoded string - optionally without decoding
the "+" sign to " " - and return the decoded string.
@class function
@name urldecode
@param str Input string in x-www-urlencoded format
@param no_plus Don't decode "+" signs to spaces
@return The decoded string
@see urlencode
]]
---[[
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.
@class function
@name urldecode_params
@param url The url or string which contains x-www-urlencoded form data
@param tbl Use the given table for storing values (optional)
@return Table containing the urldecoded parameters
@see urlencode_params
]]
---[[
Encode given string to x-www-urlencoded format.
@class function
@name urlencode
@param str String to encode
@return String containing the encoded data
@see urldecode
]]
---[[
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.
@class function
@name urlencode_params
@param tbl Table with the values
@return String containing encoded values
@see urldecode_params
]]
---[[
Creates a ltn12 source from the given socket. The source will return it's
data line by line with the trailing \r\n stripped of.
@class function
@name header_source
@param sock Readable network socket
@return Ltn12 source function
]]
---[[
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 withing 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 feeded 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 wheather the current chunk is the last one (eof)
@class function
@name mimedecode_message_body
@param src Ltn12 source function
@param msg HTTP message object
@param filecb File callback function (optional)
@return Value indicating successful operation (not nil means "ok")
@return String containing the error if unsuccessful
@see parse_message_header
]]
---[[
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 withing the given message object. Multiple parameter
values are stored as tables, ordinary ones as strings.
@class function
@name urldecode_message_body
@param src Ltn12 source function
@param msg HTTP message object
@return Value indicating successful operation (not nil means "ok")
@return String containing the error if unsuccessful
@see parse_message_header
]]
---[[
Try to extract an http message header including information like protocol
version, message headers and resulting CGI environment variables from the
given ltn12 source.
@class function
@name parse_message_header
@param src Ltn12 source function
@return HTTP message object
@see parse_message_body
]]
---[[
Try to extract and decode a http message body from the given ltn12 source.
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.
@class function
@name parse_message_body
@param src Ltn12 source function
@param msg HTTP message object
@param filecb File data callback (optional, see mimedecode_message_body())
@return Value indicating successful operation (not nil means "ok")
@return String containing the error if unsuccessful
@see parse_message_header
]]
---[[
Table containing human readable messages for several http status codes.
@class table
]]

View file

@ -0,0 +1,110 @@
-- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
-- This class provides basic ETag handling and implements most of the
-- conditional HTTP/1.1 headers specified in RFC2616 Sct. 14.24 - 14.28 .
module("luci.http.protocol.conditionals", package.seeall)
local date = require("luci.http.protocol.date")
function mk_etag( stat )
if stat ~= nil then
return string.format( '"%x-%x-%x"', stat.ino, stat.size, stat.mtime )
end
end
-- Test whether the given message object contains an "If-Match" header and
-- compare it against the given stat object.
function if_match( req, stat )
local h = req.headers
local etag = mk_etag( stat )
-- Check for matching resource
if type(h['If-Match']) == "string" then
for ent in h['If-Match']:gmatch("([^, ]+)") do
if ( ent == '*' or ent == etag ) and stat ~= nil then
return true
end
end
return false, 412
end
return true
end
-- Test whether the given message object contains an "If-Modified-Since" header
-- and compare it against the given stat object.
function if_modified_since( req, stat )
local h = req.headers
-- Compare mtimes
if type(h['If-Modified-Since']) == "string" then
local since = date.to_unix( h['If-Modified-Since'] )
if stat == nil or since < stat.mtime then
return true
end
return false, 304, {
["ETag"] = mk_etag( stat );
["Date"] = date.to_http( os.time() );
["Last-Modified"] = date.to_http( stat.mtime )
}
end
return true
end
-- Test whether the given message object contains an "If-None-Match" header and
-- compare it against the given stat object.
function if_none_match( req, stat )
local h = req.headers
local etag = mk_etag( stat )
local method = req.env and req.env.REQUEST_METHOD or "GET"
-- Check for matching resource
if type(h['If-None-Match']) == "string" then
for ent in h['If-None-Match']:gmatch("([^, ]+)") do
if ( ent == '*' or ent == etag ) and stat ~= nil then
if method == "GET" or method == "HEAD" then
return false, 304, {
["ETag"] = etag;
["Date"] = date.to_http( os.time() );
["Last-Modified"] = date.to_http( stat.mtime )
}
else
return false, 412
end
end
end
end
return true
end
-- The If-Range header is currently not implemented due to the lack of general
-- byte range stuff in luci.http.protocol . This function will always return
-- false, 412 to indicate a failed precondition.
function if_range( req, stat )
-- Sorry, no subranges (yet)
return false, 412
end
-- Test whether the given message object contains an "If-Unmodified-Since"
-- header and compare it against the given stat object.
function if_unmodified_since( req, stat )
local h = req.headers
-- Compare mtimes
if type(h['If-Unmodified-Since']) == "string" then
local since = date.to_unix( h['If-Unmodified-Since'] )
if stat ~= nil and since <= stat.mtime then
return false, 412
end
end
return true
end

View file

@ -0,0 +1,85 @@
---[[
LuCI http protocol implementation - HTTP/1.1 bits.
This class provides basic ETag handling and implements most of the
conditional HTTP/1.1 headers specified in RFC2616 Sct. 14.24 - 14.28 .
]]
module "luci.http.protocol.conditionals"
---[[
Implement 14.19 / ETag.
@class function
@name mk_etag
@param stat A file.stat structure
@return String containing the generated tag suitable for ETag headers
]]
---[[
14.24 / If-Match
Test whether the given message object contains an "If-Match" header and
compare it against the given stat object.
@class function
@name if_match
@param req HTTP request message object
@param stat A file.stat object
@return Boolean indicating whether the precondition is ok
@return Alternative status code if the precondition failed
]]
---[[
14.25 / If-Modified-Since
Test whether the given message object contains an "If-Modified-Since" header
and compare it against the given stat object.
@class function
@name if_modified_since
@param req HTTP request message object
@param stat A file.stat object
@return Boolean indicating whether the precondition is ok
@return Alternative status code if the precondition failed
@return Table containing extra HTTP headers if the precondition failed
]]
---[[
14.26 / If-None-Match
Test whether the given message object contains an "If-None-Match" header and
compare it against the given stat object.
@class function
@name if_none_match
@param req HTTP request message object
@param stat A file.stat object
@return Boolean indicating whether the precondition is ok
@return Alternative status code if the precondition failed
@return Table containing extra HTTP headers if the precondition failed
]]
---[[
14.27 / If-Range
The If-Range header is currently not implemented due to the lack of general
byte range stuff in luci.http.protocol . This function will always return
false, 412 to indicate a failed precondition.
@class function
@name if_range
@param req HTTP request message object
@param stat A file.stat object
@return Boolean indicating whether the precondition is ok
@return Alternative status code if the precondition failed
]]
---[[
14.28 / If-Unmodified-Since
Test whether the given message object contains an "If-Unmodified-Since"
header and compare it against the given stat object.
@class function
@name if_unmodified_since
@param req HTTP request message object
@param stat A file.stat object
@return Boolean indicating whether the precondition is ok
@return Alternative status code if the precondition failed
]]

View file

@ -0,0 +1,87 @@
-- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
-- This class contains functions to parse, compare and format http dates.
module("luci.http.protocol.date", package.seeall)
require("luci.sys.zoneinfo")
MONTHS = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"
}
function tz_offset(tz)
if type(tz) == "string" then
-- check for a numeric identifier
local s, v = tz:match("([%+%-])([0-9]+)")
if s == '+' then s = 1 else s = -1 end
if v then v = tonumber(v) end
if s and v then
return s * 60 * ( math.floor( v / 100 ) * 60 + ( v % 100 ) )
-- lookup symbolic tz
elseif luci.sys.zoneinfo.OFFSET[tz:lower()] then
return luci.sys.zoneinfo.OFFSET[tz:lower()]
end
end
-- bad luck
return 0
end
function to_unix(date)
local wd, day, mon, yr, hr, min, sec, tz = date:match(
"([A-Z][a-z][a-z]), ([0-9]+) " ..
"([A-Z][a-z][a-z]) ([0-9]+) " ..
"([0-9]+):([0-9]+):([0-9]+) " ..
"([A-Z0-9%+%-]+)"
)
if day and mon and yr and hr and min and sec then
-- find month
local month = 1
for i = 1, 12 do
if MONTHS[i] == mon then
month = i
break
end
end
-- convert to epoch time
return tz_offset(tz) + os.time( {
year = yr,
month = month,
day = day,
hour = hr,
min = min,
sec = sec
} )
end
return 0
end
function to_http(time)
return os.date( "%a, %d %b %Y %H:%M:%S GMT", time )
end
function compare(d1, d2)
if d1:match("[^0-9]") then d1 = to_unix(d1) end
if d2:match("[^0-9]") then d2 = to_unix(d2) end
if d1 == d2 then
return 0
elseif d1 < d2 then
return -1
else
return 1
end
end

View file

@ -0,0 +1,46 @@
---[[
LuCI http protocol implementation - date helper class.
This class contains functions to parse, compare and format http dates.
]]
module "luci.http.protocol.date"
---[[
Return the time offset in seconds between the UTC and given time zone.
@class function
@name tz_offset
@param tz Symbolic or numeric timezone specifier
@return Time offset to UTC in seconds
]]
---[[
Parse given HTTP date string and convert it to unix epoch time.
@class function
@name to_unix
@param data String containing the date
@return Unix epoch time
]]
---[[
Convert the given unix epoch time to valid HTTP date string.
@class function
@name to_http
@param time Unix epoch time
@return String containing the formatted date
]]
---[[
Compare two dates which can either be unix epoch times or HTTP date strings.
@class function
@name compare
@param d1 The first date or epoch time to compare
@param d2 The first date or epoch time to compare
@return -1 - if d1 is lower then d2
@return 0 - if both dates are equal
@return 1 - if d1 is higher then d2
]]

View file

@ -0,0 +1,78 @@
-- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
-- This class provides functions to guess mime types from file extensions and
-- vice versa.
module("luci.http.protocol.mime", package.seeall)
require("luci.util")
MIME_TYPES = {
["txt"] = "text/plain";
["js"] = "text/javascript";
["css"] = "text/css";
["htm"] = "text/html";
["html"] = "text/html";
["patch"] = "text/x-patch";
["c"] = "text/x-csrc";
["h"] = "text/x-chdr";
["o"] = "text/x-object";
["ko"] = "text/x-object";
["bmp"] = "image/bmp";
["gif"] = "image/gif";
["png"] = "image/png";
["jpg"] = "image/jpeg";
["jpeg"] = "image/jpeg";
["svg"] = "image/svg+xml";
["zip"] = "application/zip";
["pdf"] = "application/pdf";
["xml"] = "application/xml";
["xsl"] = "application/xml";
["doc"] = "application/msword";
["ppt"] = "application/vnd.ms-powerpoint";
["xls"] = "application/vnd.ms-excel";
["odt"] = "application/vnd.oasis.opendocument.text";
["odp"] = "application/vnd.oasis.opendocument.presentation";
["pl"] = "application/x-perl";
["sh"] = "application/x-shellscript";
["php"] = "application/x-php";
["deb"] = "application/x-deb";
["iso"] = "application/x-cd-image";
["tgz"] = "application/x-compressed-tar";
["mp3"] = "audio/mpeg";
["ogg"] = "audio/x-vorbis+ogg";
["wav"] = "audio/x-wav";
["mpg"] = "video/mpeg";
["mpeg"] = "video/mpeg";
["avi"] = "video/x-msvideo";
}
-- "application/octet-stream" if the extension is unknown.
function to_mime(filename)
if type(filename) == "string" then
local ext = filename:match("[^%.]+$")
if ext and MIME_TYPES[ext:lower()] then
return MIME_TYPES[ext:lower()]
end
end
return "application/octet-stream"
end
-- given mime-type is unknown.
function to_ext(mimetype)
if type(mimetype) == "string" then
for ext, type in luci.util.kspairs( MIME_TYPES ) do
if type == mimetype then
return ext
end
end
end
return nil
end

View file

@ -0,0 +1,34 @@
---[[
LuCI http protocol implementation - mime helper class.
This class provides functions to guess mime types from file extensions and
vice versa.
]]
module "luci.http.protocol.mime"
---[[
MIME mapping table containg extension - mimetype relations.
@class table
]]
---[[
Extract extension from a filename and return corresponding mime-type or
"application/octet-stream" if the extension is unknown.
@class function
@name to_mime
@param filename The filename for which the mime type is guessed
@return String containign the determined mime type
]]
---[[
Return corresponding extension for a given mime type or nil if the
given mime-type is unknown.
@class function
@name to_ext
@param mimetype The mimetype to retrieve the extension from
@return String with the extension or nil for unknown type
]]

55
luci-base/luasrc/i18n.lua Normal file
View file

@ -0,0 +1,55 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.i18n", package.seeall)
require("luci.util")
local tparser = require "luci.template.parser"
table = {}
i18ndir = luci.util.libpath() .. "/i18n/"
loaded = {}
context = luci.util.threadlocal()
default = "en"
function clear()
end
function load(file, lang, force)
end
-- Alternatively load the translation of the fallback language.
function loadc(file, force)
end
function setlanguage(lang)
context.lang = lang:gsub("_", "-")
context.parent = (context.lang:match("^([a-z][a-z])_"))
if not tparser.load_catalog(context.lang, i18ndir) then
if context.parent then
tparser.load_catalog(context.parent, i18ndir)
return context.parent
end
end
return context.lang
end
function translate(key)
return tparser.translate(key) or key
end
function translatef(key, ...)
return tostring(translate(key)):format(...)
end
-- and ensure that the returned value is a Lua string value.
-- This is the same as calling <code>tostring(translate(...))</code>
function string(key)
return tostring(translate(key))
end
-- Ensure that the returned value is a Lua string value.
-- This is the same as calling <code>tostring(translatef(...))</code>
function stringf(key, ...)
return tostring(translate(key)):format(...)
end

View file

@ -0,0 +1,84 @@
---[[
LuCI translation library.
]]
module "luci.i18n"
---[[
Clear the translation table.
@class function
@name clear
]]
---[[
Load a translation and copy its data into the translation table.
@class function
@name load
@param file Language file
@param lang Two-letter language code
@param force Force reload even if already loaded (optional)
@return Success status
]]
---[[
Load a translation file using the default translation language.
Alternatively load the translation of the fallback language.
@class function
@name loadc
@param file Language file
@param force Force reload even if already loaded (optional)
]]
---[[
Set the context default translation language.
@class function
@name setlanguage
@param lang Two-letter language code
]]
---[[
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 the translated value for a specific translation key
and ensure that the returned value is a Lua string value.
This is the same as calling <code>tostring(translate(...))</code>
@class function
@name string
@param key Default translation text
@return Translated string
]]
---[[
Return the translated value for a specific translation key and use it as sprintf pattern.
Ensure that the returned value is a Lua string value.
This is the same as calling <code>tostring(translatef(...))</code>
@class function
@name stringf
@param key Default translation text
@param ... Format parameters
@return Translated and formatted string
]]

316
luci-base/luasrc/ltn12.lua Normal file
View file

@ -0,0 +1,316 @@
--[[
LuaSocket 2.0.2 license
Copyright <EFBFBD> 2004-2007 Diego Nehab
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
]]--
--[[
Changes made by LuCI project:
* Renamed to luci.ltn12 to avoid collisions with luasocket
* Added inline documentation
]]--
-----------------------------------------------------------------------------
-- LTN12 - Filters, sources, sinks and pumps.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id$
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module
-----------------------------------------------------------------------------
local string = require("string")
local table = require("table")
local base = _G
-- See http://lua-users.org/wiki/FiltersSourcesAndSinks for design concepts
module("luci.ltn12")
filter = {}
source = {}
sink = {}
pump = {}
-- 2048 seems to be better in windows...
BLOCKSIZE = 2048
_VERSION = "LTN12 1.0.1"
-----------------------------------------------------------------------------
-- Filter stuff
-----------------------------------------------------------------------------
-- by passing it each chunk and updating a context between calls.
function filter.cycle(low, ctx, extra)
base.assert(low)
return function(chunk)
local ret
ret, ctx = low(ctx, chunk, extra)
return ret
end
end
-- (thanks to Wim Couwenberg)
function filter.chain(...)
local n = table.getn(arg)
local top, index = 1, 1
local retry = ""
return function(chunk)
retry = chunk and retry
while true do
if index == top then
chunk = arg[index](chunk)
if chunk == "" or top == n then return chunk
elseif chunk then index = index + 1
else
top = top+1
index = top
end
else
chunk = arg[index](chunk or "")
if chunk == "" then
index = index - 1
chunk = retry
elseif chunk then
if index == n then return chunk
else index = index + 1 end
else base.error("filter returned inappropriate nil") end
end
end
end
end
-----------------------------------------------------------------------------
-- Source stuff
-----------------------------------------------------------------------------
-- create an empty source
local function empty()
return nil
end
function source.empty()
return empty
end
function source.error(err)
return function()
return nil, err
end
end
function source.file(handle, io_err)
if handle then
return function()
local chunk = handle:read(BLOCKSIZE)
if chunk and chunk:len() == 0 then chunk = nil end
if not chunk then handle:close() end
return chunk
end
else return source.error(io_err or "unable to open file") end
end
function source.simplify(src)
base.assert(src)
return function()
local chunk, err_or_new = src()
src = err_or_new or src
if not chunk then return nil, err_or_new
else return chunk end
end
end
function source.string(s)
if s then
local i = 1
return function()
local chunk = string.sub(s, i, i+BLOCKSIZE-1)
i = i + BLOCKSIZE
if chunk ~= "" then return chunk
else return nil end
end
else return source.empty() end
end
function source.rewind(src)
base.assert(src)
local t = {}
return function(chunk)
if not chunk then
chunk = table.remove(t)
if not chunk then return src()
else return chunk end
else
t[#t+1] = chunk
end
end
end
function source.chain(src, f)
base.assert(src and f)
local last_in, last_out = "", ""
local state = "feeding"
local err
return function()
if not last_out then
base.error('source is empty!', 2)
end
while true do
if state == "feeding" then
last_in, err = src()
if err then return nil, err end
last_out = f(last_in)
if not last_out then
if last_in then
base.error('filter returned inappropriate nil')
else
return nil
end
elseif last_out ~= "" then
state = "eating"
if last_in then last_in = "" end
return last_out
end
else
last_out = f(last_in)
if last_out == "" then
if last_in == "" then
state = "feeding"
else
base.error('filter returned ""')
end
elseif not last_out then
if last_in then
base.error('filter returned inappropriate nil')
else
return nil
end
else
return last_out
end
end
end
end
end
-- Sources will be used one after the other, as if they were concatenated
-- (thanks to Wim Couwenberg)
function source.cat(...)
local src = table.remove(arg, 1)
return function()
while src do
local chunk, err = src()
if chunk then return chunk end
if err then return nil, err end
src = table.remove(arg, 1)
end
end
end
-----------------------------------------------------------------------------
-- Sink stuff
-----------------------------------------------------------------------------
function sink.table(t)
t = t or {}
local f = function(chunk, err)
if chunk then t[#t+1] = chunk end
return 1
end
return f, t
end
function sink.simplify(snk)
base.assert(snk)
return function(chunk, err)
local ret, err_or_new = snk(chunk, err)
if not ret then return nil, err_or_new end
snk = err_or_new or snk
return 1
end
end
function sink.file(handle, io_err)
if handle then
return function(chunk, err)
if not chunk then
handle:close()
return 1
else return handle:write(chunk) end
end
else return sink.error(io_err or "unable to open file") end
end
-- creates a sink that discards data
local function null()
return 1
end
function sink.null()
return null
end
function sink.error(err)
return function()
return nil, err
end
end
function sink.chain(f, snk)
base.assert(f and snk)
return function(chunk, err)
if chunk ~= "" then
local filtered = f(chunk)
local done = chunk and ""
while true do
local ret, snkerr = snk(filtered, err)
if not ret then return nil, snkerr end
if filtered == done then return 1 end
filtered = f(done)
end
else return 1 end
end
end
-----------------------------------------------------------------------------
-- Pump stuff
-----------------------------------------------------------------------------
function pump.step(src, snk)
local chunk, src_err = src()
local ret, snk_err = snk(chunk, src_err)
if chunk and ret then return 1
else return nil, src_err or snk_err end
end
function pump.all(src, snk, step)
base.assert(src and snk)
step = step or pump.step
while true do
local ret, err = step(src, snk)
if not ret then
if err then return nil, err
else return 1 end
end
end
end

View file

@ -0,0 +1,67 @@
-- Copyright 2011-2012 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
local map, section, net = ...
local ifc = net:get_interface()
local hostname, accept_ra, send_rs
local bcast, defaultroute, peerdns, dns, metric, clientid, vendorclass
hostname = section:taboption("general", Value, "hostname",
translate("Hostname to send when requesting DHCP"))
hostname.placeholder = luci.sys.hostname()
hostname.datatype = "hostname"
bcast = section:taboption("advanced", Flag, "broadcast",
translate("Use broadcast flag"),
translate("Required for certain ISPs, e.g. Charter with DOCSIS 3"))
bcast.default = bcast.disabled
defaultroute = section:taboption("advanced", Flag, "defaultroute",
translate("Use default gateway"),
translate("If unchecked, no default route is configured"))
defaultroute.default = defaultroute.enabled
peerdns = section:taboption("advanced", Flag, "peerdns",
translate("Use DNS servers advertised by peer"),
translate("If unchecked, the advertised DNS server addresses are ignored"))
peerdns.default = peerdns.enabled
dns = section:taboption("advanced", DynamicList, "dns",
translate("Use custom DNS servers"))
dns:depends("peerdns", "")
dns.datatype = "ipaddr"
dns.cast = "string"
metric = section:taboption("advanced", Value, "metric",
translate("Use gateway metric"))
metric.placeholder = "0"
metric.datatype = "uinteger"
clientid = section:taboption("advanced", Value, "clientid",
translate("Client ID to send when requesting DHCP"))
vendorclass = section:taboption("advanced", Value, "vendorid",
translate("Vendor Class to send when requesting DHCP"))
luci.tools.proto.opt_macaddr(section, ifc, translate("Override MAC address"))
mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU"))
mtu.placeholder = "1500"
mtu.datatype = "max(9200)"

View file

@ -0,0 +1,4 @@
-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
local map, section, net = ...

View file

@ -0,0 +1,97 @@
-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
local map, section, net = ...
local ifc = net:get_interface()
local ipaddr, netmask, gateway, broadcast, dns, accept_ra, send_rs, ip6addr, ip6gw
local mtu, metric
ipaddr = section:taboption("general", Value, "ipaddr", translate("IPv4 address"))
ipaddr.datatype = "ip4addr"
netmask = section:taboption("general", Value, "netmask",
translate("IPv4 netmask"))
netmask.datatype = "ip4addr"
netmask:value("255.255.255.0")
netmask:value("255.255.0.0")
netmask:value("255.0.0.0")
gateway = section:taboption("general", Value, "gateway", translate("IPv4 gateway"))
gateway.datatype = "ip4addr"
broadcast = section:taboption("general", Value, "broadcast", translate("IPv4 broadcast"))
broadcast.datatype = "ip4addr"
dns = section:taboption("general", DynamicList, "dns",
translate("Use custom DNS servers"))
dns.datatype = "ipaddr"
dns.cast = "string"
if luci.model.network:has_ipv6() then
local ip6assign = section:taboption("general", Value, "ip6assign", translate("IPv6 assignment length"),
translate("Assign a part of given length of every public IPv6-prefix to this interface"))
ip6assign:value("", translate("disabled"))
ip6assign:value("64")
ip6assign.datatype = "max(64)"
local ip6hint = section:taboption("general", Value, "ip6hint", translate("IPv6 assignment hint"),
translate("Assign prefix parts using this hexadecimal subprefix ID for this interface."))
for i=33,64 do ip6hint:depends("ip6assign", i) end
ip6addr = section:taboption("general", Value, "ip6addr", translate("IPv6 address"))
ip6addr.datatype = "ip6addr"
ip6addr:depends("ip6assign", "")
ip6gw = section:taboption("general", Value, "ip6gw", translate("IPv6 gateway"))
ip6gw.datatype = "ip6addr"
ip6gw:depends("ip6assign", "")
local ip6prefix = s:taboption("general", Value, "ip6prefix", translate("IPv6 routed prefix"),
translate("Public prefix routed to this device for distribution to clients."))
ip6prefix.datatype = "ip6addr"
ip6prefix:depends("ip6assign", "")
local ip6ifaceid = s:taboption("general", Value, "ip6ifaceid", translate("IPv6 suffix"),
translate("Optional. Allowed values: 'eui64', 'random', fixed value like '::1' " ..
"or '::1:2'. When IPv6 prefix (like 'a:b:c:d::') is received from a " ..
"delegating server, use the suffix (like '::1') to form the IPv6 address " ..
"('a:b:c:d::1') for the interface."))
ip6ifaceid.datatype = "ip6hostid"
ip6ifaceid.placeholder = "::1"
ip6ifaceid.rmempty = true
end
luci.tools.proto.opt_macaddr(section, ifc, translate("Override MAC address"))
mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU"))
mtu.placeholder = "1500"
mtu.datatype = "max(9200)"
metric = section:taboption("advanced", Value, "metric",
translate("Use gateway metric"))
metric.default = "1"
metric.datatype = "uinteger"
local nw = require "luci.model.network".init()
for _, network in ipairs(nw:get_networks()) do
if network:proto() == "static" and network:type() == "macvlan" and tonumber(network:metric()) >= tonumber(metric.default) then
metric.default = network:metric() + 1
end
end

View file

@ -0,0 +1,566 @@
-- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
local type, pairs, ipairs, table, luci, math
= type, pairs, ipairs, table, luci, math
local tpl = require "luci.template.parser"
local utl = require "luci.util"
local uci = require "luci.model.uci"
module "luci.model.firewall"
local uci_r, uci_s
function _valid_id(x)
return (x and #x > 0 and x:match("^[a-zA-Z0-9_]+$"))
end
function _get(c, s, o)
return uci_r:get(c, s, o)
end
function _set(c, s, o, v)
if v ~= nil then
if type(v) == "boolean" then v = v and "1" or "0" end
return uci_r:set(c, s, o, v)
else
return uci_r:delete(c, s, o)
end
end
function init(cursor)
uci_r = cursor or uci_r or uci.cursor()
uci_s = uci_r:substate()
return _M
end
function save(self, ...)
uci_r:save(...)
uci_r:load(...)
end
function commit(self, ...)
uci_r:commit(...)
uci_r:load(...)
end
function get_defaults()
return defaults()
end
function new_zone(self)
local name = "newzone"
local count = 1
while self:get_zone(name) do
count = count + 1
name = "newzone%d" % count
end
return self:add_zone(name)
end
function add_zone(self, n)
if _valid_id(n) and not self:get_zone(n) then
local d = defaults()
local z = uci_r:section("firewall", "zone", nil, {
name = n,
network = " ",
input = d:input() or "DROP",
forward = d:forward() or "DROP",
output = d:output() or "DROP"
})
return z and zone(z)
end
end
function get_zone(self, n)
if uci_r:get("firewall", n) == "zone" then
return zone(n)
else
local z
uci_r:foreach("firewall", "zone",
function(s)
if n and s.name == n then
z = s['.name']
return false
end
end)
return z and zone(z)
end
end
function get_zones(self)
local zones = { }
local znl = { }
uci_r:foreach("firewall", "zone",
function(s)
if s.name then
znl[s.name] = zone(s['.name'])
end
end)
local z
for z in utl.kspairs(znl) do
zones[#zones+1] = znl[z]
end
return zones
end
function get_zone_by_network(self, net)
local z
uci_r:foreach("firewall", "zone",
function(s)
if s.name and net then
local n
for n in utl.imatch(s.network or s.name) do
if n == net then
z = s['.name']
return false
end
end
end
end)
return z and zone(z)
end
function del_zone(self, n)
local r = false
if uci_r:get("firewall", n) == "zone" then
local z = uci_r:get("firewall", n, "name")
r = uci_r:delete("firewall", n)
n = z
else
uci_r:foreach("firewall", "zone",
function(s)
if n and s.name == n then
r = uci_r:delete("firewall", s['.name'])
return false
end
end)
end
if r then
uci_r:foreach("firewall", "rule",
function(s)
if s.src == n or s.dest == n then
uci_r:delete("firewall", s['.name'])
end
end)
uci_r:foreach("firewall", "redirect",
function(s)
if s.src == n or s.dest == n then
uci_r:delete("firewall", s['.name'])
end
end)
uci_r:foreach("firewall", "forwarding",
function(s)
if s.src == n or s.dest == n then
uci_r:delete("firewall", s['.name'])
end
end)
end
return r
end
function rename_zone(self, old, new)
local r = false
if _valid_id(new) and not self:get_zone(new) then
uci_r:foreach("firewall", "zone",
function(s)
if old and s.name == old then
if not s.network then
uci_r:set("firewall", s['.name'], "network", old)
end
uci_r:set("firewall", s['.name'], "name", new)
r = true
return false
end
end)
if r then
uci_r:foreach("firewall", "rule",
function(s)
if s.src == old then
uci_r:set("firewall", s['.name'], "src", new)
end
if s.dest == old then
uci_r:set("firewall", s['.name'], "dest", new)
end
end)
uci_r:foreach("firewall", "redirect",
function(s)
if s.src == old then
uci_r:set("firewall", s['.name'], "src", new)
end
if s.dest == old then
uci_r:set("firewall", s['.name'], "dest", new)
end
end)
uci_r:foreach("firewall", "forwarding",
function(s)
if s.src == old then
uci_r:set("firewall", s['.name'], "src", new)
end
if s.dest == old then
uci_r:set("firewall", s['.name'], "dest", new)
end
end)
end
end
return r
end
function del_network(self, net)
local z
if net then
for _, z in ipairs(self:get_zones()) do
z:del_network(net)
end
end
end
defaults = utl.class()
function defaults.__init__(self)
uci_r:foreach("firewall", "defaults",
function(s)
self.sid = s['.name']
return false
end)
self.sid = self.sid or uci_r:section("firewall", "defaults", nil, { })
end
function defaults.get(self, opt)
return _get("firewall", self.sid, opt)
end
function defaults.set(self, opt, val)
return _set("firewall", self.sid, opt, val)
end
function defaults.syn_flood(self)
return (self:get("syn_flood") == "1")
end
function defaults.drop_invalid(self)
return (self:get("drop_invalid") == "1")
end
function defaults.input(self)
return self:get("input") or "DROP"
end
function defaults.forward(self)
return self:get("forward") or "DROP"
end
function defaults.output(self)
return self:get("output") or "DROP"
end
zone = utl.class()
function zone.__init__(self, z)
if uci_r:get("firewall", z) == "zone" then
self.sid = z
self.data = uci_r:get_all("firewall", z)
else
uci_r:foreach("firewall", "zone",
function(s)
if s.name == z then
self.sid = s['.name']
self.data = s
return false
end
end)
end
end
function zone.get(self, opt)
return _get("firewall", self.sid, opt)
end
function zone.set(self, opt, val)
return _set("firewall", self.sid, opt, val)
end
function zone.masq(self)
return (self:get("masq") == "1")
end
function zone.name(self)
return self:get("name")
end
function zone.network(self)
return self:get("network")
end
function zone.input(self)
return self:get("input") or defaults():input() or "DROP"
end
function zone.forward(self)
return self:get("forward") or defaults():forward() or "DROP"
end
function zone.output(self)
return self:get("output") or defaults():output() or "DROP"
end
function zone.add_network(self, net)
if uci_r:get("network", net) == "interface" then
local nets = { }
local n
for n in utl.imatch(self:get("network") or self:get("name")) do
if n ~= net then
nets[#nets+1] = n
end
end
nets[#nets+1] = net
_M:del_network(net)
self:set("network", table.concat(nets, " "))
end
end
function zone.del_network(self, net)
local nets = { }
local n
for n in utl.imatch(self:get("network") or self:get("name")) do
if n ~= net then
nets[#nets+1] = n
end
end
if #nets > 0 then
self:set("network", table.concat(nets, " "))
else
self:set("network", " ")
end
end
function zone.get_networks(self)
local nets = { }
local n
for n in utl.imatch(self:get("network") or self:get("name")) do
nets[#nets+1] = n
end
return nets
end
function zone.clear_networks(self)
self:set("network", " ")
end
function zone.get_forwardings_by(self, what)
local name = self:name()
local forwards = { }
uci_r:foreach("firewall", "forwarding",
function(s)
if s.src and s.dest and s[what] == name then
forwards[#forwards+1] = forwarding(s['.name'])
end
end)
return forwards
end
function zone.add_forwarding_to(self, dest)
local exist, forward
for _, forward in ipairs(self:get_forwardings_by('src')) do
if forward:dest() == dest then
exist = true
break
end
end
if not exist and dest ~= self:name() and _valid_id(dest) then
local s = uci_r:section("firewall", "forwarding", nil, {
src = self:name(),
dest = dest
})
return s and forwarding(s)
end
end
function zone.add_forwarding_from(self, src)
local exist, forward
for _, forward in ipairs(self:get_forwardings_by('dest')) do
if forward:src() == src then
exist = true
break
end
end
if not exist and src ~= self:name() and _valid_id(src) then
local s = uci_r:section("firewall", "forwarding", nil, {
src = src,
dest = self:name()
})
return s and forwarding(s)
end
end
function zone.del_forwardings_by(self, what)
local name = self:name()
uci_r:delete_all("firewall", "forwarding",
function(s)
return (s.src and s.dest and s[what] == name)
end)
end
function zone.add_redirect(self, options)
options = options or { }
options.src = self:name()
local s = uci_r:section("firewall", "redirect", nil, options)
return s and redirect(s)
end
function zone.add_rule(self, options)
options = options or { }
options.src = self:name()
local s = uci_r:section("firewall", "rule", nil, options)
return s and rule(s)
end
function zone.get_color(self)
if self and self:name() == "lan" then
return "#90f090"
elseif self and self:name() == "wan" then
return "#f09090"
elseif self then
math.randomseed(tpl.hash(self:name()))
local r = math.random(128)
local g = math.random(128)
local min = 0
local max = 128
if ( r + g ) < 128 then
min = 128 - r - g
else
max = 255 - r - g
end
local b = min + math.floor( math.random() * ( max - min ) )
return "#%02x%02x%02x" % { 0xFF - r, 0xFF - g, 0xFF - b }
else
return "#eeeeee"
end
end
forwarding = utl.class()
function forwarding.__init__(self, f)
self.sid = f
end
function forwarding.src(self)
return uci_r:get("firewall", self.sid, "src")
end
function forwarding.dest(self)
return uci_r:get("firewall", self.sid, "dest")
end
function forwarding.src_zone(self)
return zone(self:src())
end
function forwarding.dest_zone(self)
return zone(self:dest())
end
rule = utl.class()
function rule.__init__(self, f)
self.sid = f
end
function rule.get(self, opt)
return _get("firewall", self.sid, opt)
end
function rule.set(self, opt, val)
return _set("firewall", self.sid, opt, val)
end
function rule.src(self)
return uci_r:get("firewall", self.sid, "src")
end
function rule.dest(self)
return uci_r:get("firewall", self.sid, "dest")
end
function rule.src_zone(self)
return zone(self:src())
end
function rule.dest_zone(self)
return zone(self:dest())
end
redirect = utl.class()
function redirect.__init__(self, f)
self.sid = f
end
function redirect.get(self, opt)
return _get("firewall", self.sid, opt)
end
function redirect.set(self, opt, val)
return _set("firewall", self.sid, opt, val)
end
function redirect.src(self)
return uci_r:get("firewall", self.sid, "src")
end
function redirect.dest(self)
return uci_r:get("firewall", self.sid, "dest")
end
function redirect.src_zone(self)
return zone(self:src())
end
function redirect.dest_zone(self)
return zone(self:dest())
end

View file

@ -0,0 +1,242 @@
-- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local os = require "os"
local io = require "io"
local fs = require "nixio.fs"
local util = require "luci.util"
local type = type
local pairs = pairs
local error = error
local table = table
local ipkg = "opkg --force-removal-of-dependent-packages --force-overwrite --nocase"
local icfg = "/etc/opkg.conf"
module "luci.model.ipkg"
-- Internal action function
local function _action(cmd, ...)
local pkg = ""
for k, v in pairs({...}) do
pkg = pkg .. " '" .. v:gsub("'", "") .. "'"
end
local c = "%s %s %s >/tmp/opkg.stdout 2>/tmp/opkg.stderr" %{ ipkg, cmd, pkg }
local r = os.execute(c)
local e = fs.readfile("/tmp/opkg.stderr")
local o = fs.readfile("/tmp/opkg.stdout")
fs.unlink("/tmp/opkg.stderr")
fs.unlink("/tmp/opkg.stdout")
return r, o or "", e or ""
end
-- Internal parser function
local function _parselist(rawdata)
if type(rawdata) ~= "function" then
error("OPKG: Invalid rawdata given")
end
local data = {}
local c = {}
local l = nil
for line in rawdata do
if line:sub(1, 1) ~= " " then
local key, val = line:match("(.-): ?(.*)%s*")
if key and val then
if key == "Package" then
c = {Package = val}
data[val] = c
elseif key == "Status" then
c.Status = {}
for j in val:gmatch("([^ ]+)") do
c.Status[j] = true
end
else
c[key] = val
end
l = key
end
else
-- Multi-line field
c[l] = c[l] .. "\n" .. line
end
end
return data
end
-- Internal lookup function
local function _lookup(act, pkg)
local cmd = ipkg .. " " .. act
if pkg then
cmd = cmd .. " '" .. pkg:gsub("'", "") .. "'"
end
-- OPKG sometimes kills the whole machine because it sucks
-- Therefore we have to use a sucky approach too and use
-- tmpfiles instead of directly reading the output
local tmpfile = os.tmpname()
os.execute(cmd .. (" >%s 2>/dev/null" % tmpfile))
local data = _parselist(io.lines(tmpfile))
os.remove(tmpfile)
return data
end
function info(pkg)
return _lookup("info", pkg)
end
function status(pkg)
return _lookup("status", pkg)
end
function install(...)
return _action("install", ...)
end
function installed(pkg)
local p = status(pkg)[pkg]
return (p and p.Status and p.Status.installed)
end
function remove(...)
return _action("remove", ...)
end
function update()
return _action("update")
end
function upgrade()
return _action("upgrade")
end
-- List helper
local function _list(action, pat, cb)
local fd = io.popen(ipkg .. " " .. action ..
(pat and (" '%s'" % pat:gsub("'", "")) or ""))
if fd then
local name, version, sz, desc
while true do
local line = fd:read("*l")
if not line then break end
name, version, sz, desc = line:match("^(.-) %- (.-) %- (.-) %- (.+)")
if not name then
name, version, sz = line:match("^(.-) %- (.-) %- (.+)")
desc = ""
end
if name and version then
if #version > 26 then
version = version:sub(1,21) .. ".." .. version:sub(-3,-1)
end
cb(name, version, sz, desc)
end
name = nil
version = nil
sz = nil
desc = nil
end
fd:close()
end
end
function list_all(pat, cb)
_list("list --size", pat, cb)
end
function list_installed(pat, cb)
_list("list_installed --size", pat, cb)
end
function find(pat, cb)
_list("find --size", pat, cb)
end
function overlay_root()
local od = "/"
local fd = io.open(icfg, "r")
if fd then
local ln
repeat
ln = fd:read("*l")
if ln and ln:match("^%s*option%s+overlay_root%s+") then
od = ln:match("^%s*option%s+overlay_root%s+(%S+)")
local s = fs.stat(od)
if not s or s.type ~= "dir" then
od = "/"
end
break
end
until not ln
fd:close()
end
return od
end
function compare_versions(ver1, comp, ver2)
if not ver1 or not ver2
or not comp or not (#comp > 0) then
error("Invalid parameters")
return nil
end
-- correct compare string
if comp == "<>" or comp == "><" or comp == "!=" or comp == "~=" then comp = "~="
elseif comp == "<=" or comp == "<" or comp == "=<" then comp = "<="
elseif comp == ">=" or comp == ">" or comp == "=>" then comp = ">="
elseif comp == "=" or comp == "==" then comp = "=="
elseif comp == "<<" then comp = "<"
elseif comp == ">>" then comp = ">"
else
error("Invalid compare string")
return nil
end
local av1 = util.split(ver1, "[%.%-]", nil, true)
local av2 = util.split(ver2, "[%.%-]", nil, true)
local max = table.getn(av1)
if (table.getn(av1) < table.getn(av2)) then
max = table.getn(av2)
end
for i = 1, max, 1 do
local s1 = av1[i] or ""
local s2 = av2[i] or ""
-- first "not equal" found return true
if comp == "~=" and (s1 ~= s2) then return true end
-- first "lower" found return true
if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
-- first "greater" found return true
if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
-- not equal then return false
if (s1 ~= s2) then return false end
end
-- all equal and not compare greater or lower then true
return not (comp == "<" or comp == ">")
end

View file

@ -0,0 +1,125 @@
---[[
LuCI OPKG call abstraction library
]]
module "luci.model.ipkg"
---[[
Return information about installed and available packages.
@class function
@name info
@param pkg Limit output to a (set of) packages
@return Table containing package information
]]
---[[
Return the package status of one or more packages.
@class function
@name status
@param pkg Limit output to a (set of) packages
@return Table containing package status information
]]
---[[
Install one or more packages.
@class function
@name install
@param ... List of packages to install
@return Boolean indicating the status of the action
@return OPKG return code, STDOUT and STDERR
]]
---[[
Determine whether a given package is installed.
@class function
@name installed
@param pkg Package
@return Boolean
]]
---[[
Remove one or more packages.
@class function
@name remove
@param ... List of packages to install
@return Boolean indicating the status of the action
@return OPKG return code, STDOUT and STDERR
]]
---[[
Update package lists.
@class function
@name update
@return Boolean indicating the status of the action
@return OPKG return code, STDOUT and STDERR
]]
---[[
Upgrades all installed packages.
@class function
@name upgrade
@return Boolean indicating the status of the action
@return OPKG return code, STDOUT and STDERR
]]
---[[
List all packages known to opkg.
@class function
@name list_all
@param pat Only find packages matching this pattern, nil lists all packages
@param cb Callback function invoked for each package, receives name, version and description as arguments
@return nothing
]]
---[[
List installed packages.
@class function
@name list_installed
@param pat Only find packages matching this pattern, nil lists all packages
@param cb Callback function invoked for each package, receives name, version and description as arguments
@return nothing
]]
---[[
Find packages that match the given pattern.
@class function
@name find
@param pat Find packages whose names or descriptions match this pattern, nil results in zero results
@param cb Callback function invoked for each patckage, receives name, version and description as arguments
@return nothing
]]
---[[
Determines the overlay root used by opkg.
@class function
@name overlay_root
@return String containing the directory path of the overlay root.
]]
---[[
lua version of opkg compare-versions
@class function
@name compare_versions
@param ver1 string version 1
@param ver2 string version 2
@param comp string compare versions using
"<=" or "<" lower-equal
">" or ">=" greater-equal
"=" equal
"<<" lower
">>" greater
"~=" not equal
@return Boolean indicating the status of the compare
]]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,236 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local os = require "os"
local uci = require "uci"
local util = require "luci.util"
local table = require "table"
local setmetatable, rawget, rawset = setmetatable, rawget, rawset
local require, getmetatable = require, getmetatable
local error, pairs, ipairs = error, pairs, ipairs
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 deamons etc. are
-- reloaded.
module "luci.model.uci"
cursor = uci.cursor
APIVERSION = uci.APIVERSION
function cursor_state()
return cursor(nil, "/var/state")
end
inst = cursor()
inst_state = cursor_state()
local Cursor = getmetatable(inst)
function Cursor.apply(self, configlist, command)
configlist = self:_affected(configlist)
if command then
return { "/sbin/luci-reload", unpack(configlist) }
else
return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
% table.concat(configlist, " "))
end
end
-- returns a boolean whether to delete the current section (optional)
function Cursor.delete_all(self, config, stype, comparator)
local del = {}
if type(comparator) == "table" then
local tbl = comparator
comparator = function(section)
for k, v in pairs(tbl) do
if section[k] ~= v then
return false
end
end
return true
end
end
local function helper (section)
if not comparator or comparator(section) then
del[#del+1] = section[".name"]
end
end
self:foreach(config, stype, helper)
for i, j in ipairs(del) do
self:delete(config, j)
end
end
function Cursor.section(self, config, type, name, values)
local stat = true
if name then
stat = self:set(config, name, type)
else
name = self:add(config, type)
stat = name and true
end
if stat and values then
stat = self:tset(config, name, values)
end
return stat and name
end
function Cursor.tset(self, config, section, values)
local stat = true
for k, v in pairs(values) do
if k:sub(1, 1) ~= "." then
stat = stat and self:set(config, section, k, v)
end
end
return stat
end
function Cursor.get_bool(self, ...)
local val = self:get(...)
return ( val == "1" or val == "true" or val == "yes" or val == "on" )
end
function Cursor.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 Cursor.get_first(self, conf, stype, opt, def)
local rv = def
self:foreach(conf, stype,
function(s)
local val = not opt and s['.name'] or s[opt]
if type(def) == "number" then
val = tonumber(val)
elseif type(def) == "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 Cursor.set_list(self, config, section, option, value)
if config and section and option then
if not value or #value == 0 then
return self:delete(config, section, option)
end
return self:set(
config, section, option,
( type(value) == "table" and value or { value } )
)
end
return false
end
-- Return a list of initscripts affected by configuration changes.
function Cursor._affected(self, configlist)
configlist = type(configlist) == "table" and configlist or {configlist}
local c = cursor()
c:load("ucitrack")
-- Resolve dependencies
local reloadlist = {}
local function _resolve_deps(name)
local reload = {name}
local deps = {}
c:foreach("ucitrack", name,
function(section)
if section.affects then
for i, aff in ipairs(section.affects) do
deps[#deps+1] = aff
end
end
end)
for i, dep in ipairs(deps) do
for j, add in ipairs(_resolve_deps(dep)) do
reload[#reload+1] = add
end
end
return reload
end
-- Collect initscripts
for j, config in ipairs(configlist) do
for i, e in ipairs(_resolve_deps(config)) do
if not util.contains(reloadlist, e) then
reloadlist[#reloadlist+1] = e
end
end
end
return reloadlist
end
-- curser, means it the parent unloads or loads configs, the sub state will
-- do so as well.
function Cursor.substate(self)
Cursor._substates = Cursor._substates or { }
Cursor._substates[self] = Cursor._substates[self] or cursor_state()
return Cursor._substates[self]
end
local _load = Cursor.load
function Cursor.load(self, ...)
if Cursor._substates and Cursor._substates[self] then
_load(Cursor._substates[self], ...)
end
return _load(self, ...)
end
local _unload = Cursor.unload
function Cursor.unload(self, ...)
if Cursor._substates and Cursor._substates[self] then
_unload(Cursor._substates[self], ...)
end
return _unload(self, ...)
end

View file

@ -0,0 +1,299 @@
---[[
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 deamons 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
@class function
@name Cursor.apply
@param configlist List of UCI configurations
@param command Don't apply only return the command
]]
---[[
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. Raw values will become a single item table.
@return Boolean whether operation succeeded
]]
---[[
Create a sub-state of this cursor. The sub-state is tied to the parent
curser, 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
]]
---[[
Set the configuration directory.
@class function
@name Cursor.set_confdir
@param directory UCI configuration directory
@return Boolean whether operation succeeded
]]
---[[
Set the directory for uncommited changes.
@class function
@name Cursor.set_savedir
@param directory UCI changes directory
@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
]]

View file

@ -0,0 +1,73 @@
-- 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

View file

@ -0,0 +1,89 @@
-- 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
}
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

View file

@ -0,0 +1,6 @@
-- 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)

688
luci-base/luasrc/sys.lua Normal file
View file

@ -0,0 +1,688 @@
-- 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 =
tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
module "luci.sys"
function call(...)
return os.execute(...) / 256
end
exec = luci.util.exec
function mounts()
local data = {}
local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
local ps = luci.util.execi("df")
if not ps then
return
else
ps()
end
for line in ps do
local row = {}
local j = 1
for value in line:gmatch("[^%s]+") do
row[k[j]] = value
j = j + 1
end
if row[k[1]] then
-- this is a rather ugly workaround to cope with wrapped lines in
-- the df output:
--
-- /dev/scsi/host0/bus0/target0/lun0/part3
-- 114382024 93566472 15005244 86% /mnt/usb
--
if not row[k[2]] then
j = 2
line = ps()
for value in line:gmatch("[^%s]+") do
row[k[j]] = value
j = j + 1
end
end
table.insert(data, row)
end
end
return data
end
-- 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- '"..url:gsub("'", "").."'")
else
return os.execute("wget -qO '%s' '%s'" %
{target:gsub("'", ""), url:gsub("'", "")})
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 = {}
-- The following fields are defined for arp entry objects:
-- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
function net.arptable(callback)
local arp = (not callback) and {} or nil
local e, r, v
if fs.access("/proc/net/arp") then
for e in io.lines("/proc/net/arp") do
local r = { }, v
for v in e:gmatch("%S+") do
r[#r+1] = v
end
if r[1] ~= "IP" then
local x = {
["IP address"] = r[1],
["HW type"] = r[2],
["Flags"] = r[3],
["HW address"] = r[4],
["Mask"] = r[5],
["Device"] = r[6]
}
if callback then
callback(x)
else
arp = arp or { }
arp[#arp+1] = x
end
end
end
end
return arp
end
local function _nethints(what, callback)
local _, k, e, mac, ip, name
local cur = uci.cursor()
local ifn = { }
local hosts = { }
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:upper(), neigh.dest:string(), nil, nil)
elseif neigh.mac and neigh.family == 6 then
_add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
end
end)
if fs.access("/etc/ethers") then
for e in io.lines("/etc/ethers") do
mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
if mac and ip then
_add(what, mac:upper(), ip, nil, nil)
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+)")
if mac and ip then
_add(what, mac:upper(), ip, nil, name ~= "*" and name)
end
end
end
end
)
cur:foreach("dhcp", "host",
function(s)
for mac in luci.util.imatch(s.mac) do
_add(what, mac:upper(), s.ip, nil, s.name)
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 luci.util.kspairs(hosts) do
callback(e[1], e[2], e[3], 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 nixio.getnameinfo(v4 or v6, nil, 100) or v4
if name and name ~= mac then
callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
end
end)
else
local rv = { }
_nethints(1, function(mac, v4, v6, name)
name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
if name and name ~= mac then
rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) 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 nixio.getnameinfo(v4, nil, 100) 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 nixio.getnameinfo(v4, nil, 100) 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 nixio.getnameinfo(v6, nil, 100) 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 nixio.getnameinfo(v6, nil, 100) 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, timeout, tuples =
line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
if fam and l3 and l4 and timeout 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 = {}
for k, v in ipairs(nixio.getifaddrs()) do
if v.family == "packet" then
devs[#devs+1] = v.name
end
end
return devs
end
function net.deviceinfo()
local devs = {}
for k, v in ipairs(nixio.getifaddrs()) do
if v.family == "packet" then
local d = v.data
d[1] = d.rx_bytes
d[2] = d.rx_packets
d[3] = d.rx_errors
d[4] = d.rx_dropped
d[5] = 0
d[6] = 0
d[7] = 0
d[8] = d.multicast
d[9] = d.tx_bytes
d[10] = d.tx_packets
d[11] = d.tx_errors
d[12] = d.tx_dropped
d[13] = 0
d[14] = d.collisions
d[15] = 0
d[16] = 0
devs[v.name] = d
end
end
return devs
end
-- The following fields are defined for route entry tables:
-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
-- "flags", "device" }
function net.routes(callback)
local routes = { }
for line in io.lines("/proc/net/route") do
local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
dst_mask, mtu, win, irtt = line:match(
"([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
"(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
)
if dev then
gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
dst_ip = luci.ip.Hex(
dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
)
local rt = {
dest = dst_ip,
gateway = gateway,
metric = tonumber(metric),
refcount = tonumber(refcnt),
usecount = tonumber(usecnt),
mtu = tonumber(mtu),
window = tonumber(window),
irtt = tonumber(irtt),
flags = tonumber(flags, 16),
device = dev
}
if callback then
callback(rt)
else
routes[#routes+1] = rt
end
end
end
return routes
end
-- The following fields are defined for route entry tables:
-- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
-- "flags", "device" }
function net.routes6(callback)
if fs.access("/proc/net/ipv6_route", "r") then
local routes = { }
for line in io.lines("/proc/net/ipv6_route") do
local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
metric, refcnt, usecnt, flags, dev = line:match(
"([a-f0-9]+) ([a-f0-9]+) " ..
"([a-f0-9]+) ([a-f0-9]+) " ..
"([a-f0-9]+) ([a-f0-9]+) " ..
"([a-f0-9]+) ([a-f0-9]+) " ..
"([a-f0-9]+) +([^%s]+)"
)
if dst_ip and dst_prefix and
src_ip and src_prefix and
nexthop and metric and
refcnt and usecnt and
flags and dev
then
src_ip = luci.ip.Hex(
src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
)
dst_ip = luci.ip.Hex(
dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
)
nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
local rt = {
source = src_ip,
dest = dst_ip,
nexthop = nexthop,
metric = tonumber(metric, 16),
refcount = tonumber(refcnt, 16),
usecount = tonumber(usecnt, 16),
flags = tonumber(flags, 16),
device = dev,
-- lua number is too small for storing the metric
-- add a metric_raw field with the original content
metric_raw = metric
}
if callback then
callback(rt)
else
routes[#routes+1] = rt
end
end
end
return routes
end
end
function net.pingtest(host)
return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
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][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
)
local idx = tonumber(pid)
if idx 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
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 or pwh == "!" or pwh == "x" 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)
if password then
password = password:gsub("'", [['"'"']])
end
if username then
username = username:gsub("'", [['"'"']])
end
return os.execute(
"(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
"passwd '" .. username .. "' >/dev/null 2>&1"
)
end
wifi = {}
function wifi.getiwinfo(ifname)
local stat, iwinfo = pcall(require, "iwinfo")
if ifname then
local d, n = ifname:match("^(%w+)%.network(%d+)")
local wstate = luci.util.ubus("network.wireless", "status") or { }
d = d or ifname
n = n and tonumber(n) or 1
if type(wstate[d]) == "table" and
type(wstate[d].interfaces) == "table" and
type(wstate[d].interfaces[n]) == "table" and
type(wstate[d].interfaces[n].ifname) == "string"
then
ifname = wstate[d].interfaces[n].ifname
else
ifname = d
end
local t = stat and iwinfo.type(ifname)
local x = t and iwinfo[t] or { }
return setmetatable({}, {
__index = function(t, k)
if k == "ifname" then
return ifname
elseif x[k] then
return x[k](ifname)
end
end
})
end
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)
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)
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) == 1)
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

405
luci-base/luasrc/sys.luadoc Normal file
View file

@ -0,0 +1,405 @@
---[[
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 containg 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 containg 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 the current arp-table entries as two-dimensional table.
@class function
@name net.arptable
@return Table of table containing the current arp entries.
-- The following fields are defined for arp entry objects:
-- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
]]
---[[
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
]]
---[[
Return information about available network interfaces.
@class function
@name net.deviceinfo
@return Table containing all current interface names and their information
]]
---[[
Returns the current kernel routing table entries.
@class function
@name net.routes
@return Table of tables with properties of the corresponding routes.
-- The following fields are defined for route entry tables:
-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
-- "flags", "device" }
]]
---[[
Returns the current ipv6 kernel routing table entries.
@class function
@name net.routes6
@return Table of tables with properties of the corresponding routes.
-- The following fields are defined for route entry tables:
-- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
-- "flags", "device" }
]]
---[[
Tests whether the given host responds to ping probes.
@class function
@name net.pingtest
@param host String containing a hostname or IPv4 address
@return Number containing 0 on success and >= 1 on error
]]
---[[
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
]]
---[[
LuCI system utilities / user related functions.
@class module
@name luci.sys.user
]]
---[[
Retrieve user informations 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 wheather 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
]]

View file

@ -0,0 +1,374 @@
--[[
Iptables parser and query library
(c) 2008-2009 Jo-Philipp Wich <jow@openwrt.org>
(c) 2008-2009 Steven Barth <steven@midlink.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
$Id$
]]--
local luci = {}
luci.util = require "luci.util"
luci.sys = require "luci.sys"
luci.ip = require "luci.ip"
local pcall = pcall
local io = require "io"
local tonumber, ipairs, table = tonumber, ipairs, table
module("luci.sys.iptparser")
IptParser = luci.util.class()
function IptParser.__init__( self, family )
self._family = (tonumber(family) == 6) and 6 or 4
self._rules = { }
self._chains = { }
self._tables = { }
local t = self._tables
local s = self:_supported_tables(self._family)
if s.filter then t[#t+1] = "filter" end
if s.nat then t[#t+1] = "nat" end
if s.mangle then t[#t+1] = "mangle" end
if s.raw then t[#t+1] = "raw" end
if self._family == 4 then
self._nulladdr = "0.0.0.0/0"
self._command = "iptables -t %s --line-numbers -nxvL"
else
self._nulladdr = "::/0"
self._command = "ip6tables -t %s --line-numbers -nxvL"
end
self:_parse_rules()
end
function IptParser._supported_tables( self, family )
local tables = { }
local ok, lines = pcall(io.lines,
(family == 6) and "/proc/net/ip6_tables_names"
or "/proc/net/ip_tables_names")
if ok and lines then
local line
for line in lines do
tables[line] = true
end
end
return tables
end
-- search criteria as only argument. If args is nil or an empty table then all
-- rules will be returned.
--
-- The following keys in the args table are recognized:
-- <ul>
-- <li> table - Match rules that are located within the given table
-- <li> chain - Match rules that are located within the given chain
-- <li> target - Match rules with the given target
-- <li> protocol - Match rules that match the given protocol, rules with
-- protocol "all" are always matched
-- <li> source - Match rules with the given source, rules with source
-- "0.0.0.0/0" (::/0) are always matched
-- <li> destination - Match rules with the given destination, rules with
-- destination "0.0.0.0/0" (::/0) are always matched
-- <li> inputif - Match rules with the given input interface, rules
-- with input interface "*" (=all) are always matched
-- <li> outputif - Match rules with the given output interface, rules
-- with output interface "*" (=all) are always matched
-- <li> flags - Match rules that match the given flags, current
-- supported values are "-f" (--fragment)
-- and "!f" (! --fragment)
-- <li> options - Match rules containing all given options
-- </ul>
-- The return value is a list of tables representing the matched rules.
-- Each rule table contains the following fields:
-- <ul>
-- <li> index - The index number of the rule
-- <li> table - The table where the rule is located, can be one
-- of "filter", "nat" or "mangle"
-- <li> chain - The chain where the rule is located, e.g. "INPUT"
-- or "postrouting_wan"
-- <li> target - The rule target, e.g. "REJECT" or "DROP"
-- <li> protocol The matching protocols, e.g. "all" or "tcp"
-- <li> flags - Special rule options ("--", "-f" or "!f")
-- <li> inputif - Input interface of the rule, e.g. "eth0.0"
-- or "*" for all interfaces
-- <li> outputif - Output interface of the rule,e.g. "eth0.0"
-- or "*" for all interfaces
-- <li> source - The source ip range, e.g. "0.0.0.0/0" (::/0)
-- <li> destination - The destination ip range, e.g. "0.0.0.0/0" (::/0)
-- <li> options - A list of specific options of the rule,
-- e.g. { "reject-with", "tcp-reset" }
-- <li> packets - The number of packets matched by the rule
-- <li> bytes - The number of total bytes matched by the rule
-- </ul>
-- Example:
-- <pre>
-- ip = luci.sys.iptparser.IptParser()
-- result = ip.find( {
-- target="REJECT",
-- protocol="tcp",
-- options={ "reject-with", "tcp-reset" }
-- } )
-- </pre>
-- This will match all rules with target "-j REJECT",
-- protocol "-p tcp" (or "-p all")
-- and the option "--reject-with tcp-reset".
function IptParser.find( self, args )
local args = args or { }
local rv = { }
args.source = args.source and self:_parse_addr(args.source)
args.destination = args.destination and self:_parse_addr(args.destination)
for i, rule in ipairs(self._rules) do
local match = true
-- match table
if not ( not args.table or args.table:lower() == rule.table ) then
match = false
end
-- match chain
if not ( match == true and (
not args.chain or args.chain == rule.chain
) ) then
match = false
end
-- match target
if not ( match == true and (
not args.target or args.target == rule.target
) ) then
match = false
end
-- match protocol
if not ( match == true and (
not args.protocol or rule.protocol == "all" or
args.protocol:lower() == rule.protocol
) ) then
match = false
end
-- match source
if not ( match == true and (
not args.source or rule.source == self._nulladdr or
self:_parse_addr(rule.source):contains(args.source)
) ) then
match = false
end
-- match destination
if not ( match == true and (
not args.destination or rule.destination == self._nulladdr or
self:_parse_addr(rule.destination):contains(args.destination)
) ) then
match = false
end
-- match input interface
if not ( match == true and (
not args.inputif or rule.inputif == "*" or
args.inputif == rule.inputif
) ) then
match = false
end
-- match output interface
if not ( match == true and (
not args.outputif or rule.outputif == "*" or
args.outputif == rule.outputif
) ) then
match = false
end
-- match flags (the "opt" column)
if not ( match == true and (
not args.flags or rule.flags == args.flags
) ) then
match = false
end
-- match specific options
if not ( match == true and (
not args.options or
self:_match_options( rule.options, args.options )
) ) then
match = false
end
-- insert match
if match == true then
rv[#rv+1] = rule
end
end
return rv
end
-- through external commands.
function IptParser.resync( self )
self._rules = { }
self._chain = nil
self:_parse_rules()
end
function IptParser.tables( self )
return self._tables
end
function IptParser.chains( self, table )
local lookup = { }
local chains = { }
for _, r in ipairs(self:find({table=table})) do
if not lookup[r.chain] then
lookup[r.chain] = true
chains[#chains+1] = r.chain
end
end
return chains
end
-- and "rules". The "rules" field is a table of rule tables.
function IptParser.chain( self, table, chain )
return self._chains[table:lower()] and self._chains[table:lower()][chain]
end
function IptParser.is_custom_target( self, target )
for _, r in ipairs(self._rules) do
if r.chain == target then
return true
end
end
return false
end
-- [internal] Parse address according to family.
function IptParser._parse_addr( self, addr )
if self._family == 4 then
return luci.ip.IPv4(addr)
else
return luci.ip.IPv6(addr)
end
end
-- [internal] Parse iptables output from all tables.
function IptParser._parse_rules( self )
for i, tbl in ipairs(self._tables) do
self._chains[tbl] = { }
for i, rule in ipairs(luci.util.execl(self._command % tbl)) do
if rule:find( "^Chain " ) == 1 then
local crefs
local cname, cpol, cpkt, cbytes = rule:match(
"^Chain ([^%s]*) %(policy (%w+) " ..
"(%d+) packets, (%d+) bytes%)"
)
if not cname then
cname, crefs = rule:match(
"^Chain ([^%s]*) %((%d+) references%)"
)
end
self._chain = cname
self._chains[tbl][cname] = {
policy = cpol,
packets = tonumber(cpkt or 0),
bytes = tonumber(cbytes or 0),
references = tonumber(crefs or 0),
rules = { }
}
else
if rule:find("%d") == 1 then
local rule_parts = luci.util.split( rule, "%s+", nil, true )
local rule_details = { }
-- cope with rules that have no target assigned
if rule:match("^%d+%s+%d+%s+%d+%s%s") then
table.insert(rule_parts, 4, nil)
end
-- ip6tables opt column is usually zero-width
if self._family == 6 then
table.insert(rule_parts, 6, "--")
end
rule_details["table"] = tbl
rule_details["chain"] = self._chain
rule_details["index"] = tonumber(rule_parts[1])
rule_details["packets"] = tonumber(rule_parts[2])
rule_details["bytes"] = tonumber(rule_parts[3])
rule_details["target"] = rule_parts[4]
rule_details["protocol"] = rule_parts[5]
rule_details["flags"] = rule_parts[6]
rule_details["inputif"] = rule_parts[7]
rule_details["outputif"] = rule_parts[8]
rule_details["source"] = rule_parts[9]
rule_details["destination"] = rule_parts[10]
rule_details["options"] = { }
for i = 11, #rule_parts do
if #rule_parts[i] > 0 then
rule_details["options"][i-10] = rule_parts[i]
end
end
self._rules[#self._rules+1] = rule_details
self._chains[tbl][self._chain].rules[
#self._chains[tbl][self._chain].rules + 1
] = rule_details
end
end
end
end
self._chain = nil
end
-- [internal] Return true if optlist1 contains all elements of optlist 2.
-- Return false in all other cases.
function IptParser._match_options( self, o1, o2 )
-- construct a hashtable of first options list to speed up lookups
local oh = { }
for i, opt in ipairs( o1 ) do oh[opt] = true end
-- iterate over second options list
-- each string in o2 must be also present in o1
-- if o2 contains a string which is not found in o1 then return false
for i, opt in ipairs( o2 ) do
if not oh[opt] then
return false
end
end
return true
end

View file

@ -0,0 +1,69 @@
---[[
LuCI iptables parser and query library
@cstyle instance
]]
module "luci.sys.iptparser"
---[[
Create a new iptables parser object.
@class function
@name IptParser
@param family Number specifying the address family. 4 for IPv4, 6 for IPv6
@return IptParser instance
]]
---[[
Find all firewall rules that match the given criteria. Expects a table with
search criteria as only argument. If args is nil or an empty table then all
rules will be returned.
]]
---[[
Rebuild the internal lookup table, for example when rules have changed
through external commands.
@class function
@name IptParser.resync
@return nothing
]]
---[[
Find the names of all tables.
@class function
@name IptParser.tables
@return Table of table names.
]]
---[[
Find the names of all chains within the given table name.
@class function
@name IptParser.chains
@param table String containing the table name
@return Table of chain names in the order they occur.
]]
---[[
Return the given firewall chain within the given table name.
@class function
@name IptParser.chain
@param table String containing the table name
@param chain String containing the chain name
@return Table containing the fields "policy", "packets", "bytes"
-- and "rules". The "rules" field is a table of rule tables.
]]
---[[
Test whether the given target points to a custom chain.
@class function
@name IptParser.is_custom_target
@param target String containing the target action
@return Boolean indicating whether target is a custom chain.
]]

View file

@ -0,0 +1,19 @@
-- 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
})

View file

@ -0,0 +1,457 @@
-- 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', 'WET0WEST,M3.5.0,M10.5.0/3' },
{ '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', 'WET0WEST,M3.5.0,M10.5.0/3' },
{ 'Africa/Freetown', 'GMT0' },
{ 'Africa/Gaborone', 'CAT-2' },
{ 'Africa/Harare', 'CAT-2' },
{ 'Africa/Johannesburg', 'SAST-2' },
{ 'Africa/Juba', 'EAT-3' },
{ '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<-03>,M10.3.0/0,M2.3.0/0' },
{ '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<-03>,M10.3.0/0,M2.3.0/0' },
{ 'America/Curacao', 'AST4' },
{ 'America/Danmarkshavn', 'GMT0' },
{ 'America/Dawson', 'PST8PDT,M3.2.0,M11.1.0' },
{ '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/Godthab', '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1' },
{ '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/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>,M8.2.6/24,M5.2.6/24' },
{ 'America/Santo Domingo', 'AST4' },
{ 'America/Sao Paulo', '<-03>3<-02>,M10.3.0/0,M2.3.0/0' },
{ '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', 'PST8PDT,M3.2.0,M11.1.0' },
{ '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', '<+11>-11' },
{ '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', 'EET-2EEST,M3.5.4/24,M10.5.5/1' },
{ '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', 'EET-2EEST,M3.5.5/0,M10.5.5/0' },
{ '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.5.6/1,M10.5.6/1' },
{ 'Asia/Hebron', 'EET-2EEST,M3.5.6/1,M10.5.6/1' },
{ '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', '<+08>-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-8:30' },
{ 'Asia/Qatar', '<+03>-3' },
{ 'Asia/Qyzylorda', '<+06>-6' },
{ '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<+0430>,J80/0,J264/0' },
{ '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/Currie', 'AEST-10AEDT,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', 'GMT0IST,M3.5.0/1,M10.5.0' },
{ '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/Kiev', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
{ 'Europe/Kirov', '<+03>-3' },
{ '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/Uzhgorod', 'EET-2EEST,M3.5.0/3,M10.5.0/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/Zaporozhye', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
{ '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<+14>,M9.5.0/3,M4.1.0/4' },
{ '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>,M8.2.6/22,M5.2.6/22' },
{ 'Pacific/Efate', '<+11>-11' },
{ 'Pacific/Enderbury', '<+13>-13' },
{ 'Pacific/Fakaofo', '<+13>-13' },
{ 'Pacific/Fiji', '<+12>-12<+13>,M11.1.0,M1.2.1/147' },
{ 'Pacific/Funafuti', '<+12>-12' },
{ 'Pacific/Galapagos', '<-06>6' },
{ 'Pacific/Gambier', '<-09>9' },
{ 'Pacific/Guadalcanal', '<+11>-11' },
{ 'Pacific/Guam', 'ChST-10' },
{ 'Pacific/Honolulu', 'HST10' },
{ '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' },
{ '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' },
}

View file

@ -0,0 +1,45 @@
-- 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
wet = 0, -- WET
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
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 = 30600, -- KST
jst = 32400, -- JST
acst = 34200, -- ACST
acdt = 37800, -- ACDT
aest = 36000, -- AEST
awst = 28800, -- AWST
msk = 10800, -- MSK
sst = -39600, -- SST
}

View file

@ -0,0 +1,100 @@
-- 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 '" .. 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 occured: " .. tostring(err or "(nil)"))
end
end

View file

@ -0,0 +1,36 @@
-- Copyright 2012 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.tools.proto", package.seeall)
function opt_macaddr(s, ifc, ...)
local v = luci.cbi.Value
local o = s:taboption("advanced", v, "macaddr", ...)
o.placeholder = ifc and ifc:mac()
o.datatype = "macaddr"
function o.cfgvalue(self, section)
local w = ifc and ifc:get_wifinet()
if w then
return w:get("macaddr")
else
return v.cfgvalue(self, section)
end
end
function o.write(self, section, value)
local w = ifc and ifc:get_wifinet()
if w then
w:set("macaddr", value)
elseif value then
v.write(self, section, value)
else
v.remove(self, section)
end
end
function o.remove(self, section)
self:write(section, nil)
end
end

View file

@ -0,0 +1,230 @@
-- Copyright 2011 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.
module("luci.tools.status", package.seeall)
local uci = require "luci.model.uci".cursor()
local function dhcp_leases_common(family)
local rv = { }
local nfs = require "nixio.fs"
local leasefile = "/tmp/dhcp.leases"
uci:foreach("dhcp", "dnsmasq",
function(s)
if s.leasefile and nfs.access(s.leasefile) then
leasefile = s.leasefile
return false
end
end)
local fd = io.open(leasefile, "r")
if fd then
while true do
local ln = fd:read("*l")
if not ln then
break
else
local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)")
local expire = tonumber(ts) or 0
if ts and mac and ip and name and duid then
if family == 4 and not ip:match(":") then
rv[#rv+1] = {
expires = (expire ~= 0) and os.difftime(expire, os.time()),
macaddr = mac,
ipaddr = ip,
hostname = (name ~= "*") and name
}
elseif family == 6 and ip:match(":") then
rv[#rv+1] = {
expires = (expire ~= 0) and os.difftime(expire, os.time()),
ip6addr = ip,
duid = (duid ~= "*") and duid,
hostname = (name ~= "*") and name
}
end
end
end
end
fd:close()
end
local lease6file = "/tmp/hosts/odhcpd"
uci:foreach("dhcp", "odhcpd",
function(t)
if t.leasefile and nfs.access(t.leasefile) then
lease6file = t.leasefile
return false
end
end)
local fd = io.open(lease6file, "r")
if fd then
while true do
local ln = fd:read("*l")
if not ln then
break
else
local iface, duid, iaid, name, ts, id, length, ip = ln:match("^# (%S+) (%S+) (%S+) (%S+) (-?%d+) (%S+) (%S+) (.*)")
local expire = tonumber(ts) or 0
if ip and iaid ~= "ipv4" and family == 6 then
rv[#rv+1] = {
expires = (expire >= 0) and os.difftime(expire, os.time()),
duid = duid,
ip6addr = ip,
hostname = (name ~= "-") and name
}
elseif ip and iaid == "ipv4" and family == 4 then
local mac, mac1, mac2, mac3, mac4, mac5, mac6
if duid and type(duid) == "string" then
mac1, mac2, mac3, mac4, mac5, mac6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
end
if not (mac1 and mac2 and mac3 and mac4 and mac5 and mac6) then
mac = "FF:FF:FF:FF:FF:FF"
else
mac = mac1..":"..mac2..":"..mac3..":"..mac4..":"..mac5..":"..mac6
end
rv[#rv+1] = {
expires = (expire >= 0) and os.difftime(expire, os.time()),
macaddr = duid,
macaddr = mac:lower(),
ipaddr = ip,
hostname = (name ~= "-") and name
}
end
end
end
fd:close()
end
return rv
end
function dhcp_leases()
return dhcp_leases_common(4)
end
function dhcp6_leases()
return dhcp_leases_common(6)
end
function wifi_networks()
local rv = { }
local ntm = require "luci.model.network".init()
local dev
for _, dev in ipairs(ntm:get_wifidevs()) do
local rd = {
up = dev:is_up(),
device = dev:name(),
name = dev:get_i18n(),
networks = { }
}
local net
for _, net in ipairs(dev:get_wifinets()) do
rd.networks[#rd.networks+1] = {
name = net:shortname(),
link = net:adminlink(),
up = net:is_up(),
mode = net:active_mode(),
ssid = net:active_ssid(),
bssid = net:active_bssid(),
encryption = net:active_encryption(),
frequency = net:frequency(),
channel = net:channel(),
signal = net:signal(),
quality = net:signal_percent(),
noise = net:noise(),
bitrate = net:bitrate(),
ifname = net:ifname(),
assoclist = net:assoclist(),
country = net:country(),
txpower = net:txpower(),
txpoweroff = net:txpower_offset(),
disabled = (dev:get("disabled") == "1" or
net:get("disabled") == "1")
}
end
rv[#rv+1] = rd
end
return rv
end
function wifi_network(id)
local ntm = require "luci.model.network".init()
local net = ntm:get_wifinet(id)
if net then
local dev = net:get_device()
if dev then
return {
id = id,
name = net:shortname(),
link = net:adminlink(),
up = net:is_up(),
mode = net:active_mode(),
ssid = net:active_ssid(),
bssid = net:active_bssid(),
encryption = net:active_encryption(),
frequency = net:frequency(),
channel = net:channel(),
signal = net:signal(),
quality = net:signal_percent(),
noise = net:noise(),
bitrate = net:bitrate(),
ifname = net:ifname(),
assoclist = net:assoclist(),
country = net:country(),
txpower = net:txpower(),
txpoweroff = net:txpower_offset(),
disabled = (dev:get("disabled") == "1" or
net:get("disabled") == "1"),
device = {
up = dev:is_up(),
device = dev:name(),
name = dev:get_i18n()
}
}
end
end
return { }
end
function switch_status(devs)
local dev
local switches = { }
for dev in devs:gmatch("[^%s,]+") do
local ports = { }
local swc = io.popen("swconfig dev %q show" % dev, "r")
if swc then
local l
repeat
l = swc:read("*l")
if l then
local port, up = l:match("port:(%d+) link:(%w+)")
if port then
local speed = l:match(" speed:(%d+)")
local duplex = l:match(" (%w+)-duplex")
local txflow = l:match(" (txflow)")
local rxflow = l:match(" (rxflow)")
local auto = l:match(" (auto)")
ports[#ports+1] = {
port = tonumber(port) or 0,
speed = tonumber(speed) or 0,
link = (up == "up"),
duplex = (duplex == "full"),
rxflow = (not not rxflow),
txflow = (not not txflow),
auto = (not not auto)
}
end
end
until not l
swc:close()
end
switches[dev] = ports
end
return switches
end

View file

@ -0,0 +1,105 @@
-- 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.
module("luci.tools.webadmin", package.seeall)
local util = require "luci.util"
local uci = require "luci.model.uci"
local ip = require "luci.ip"
function byte_format(byte)
local suff = {"B", "KB", "MB", "GB", "TB"}
for i=1, 5 do
if byte > 1024 and i < 5 then
byte = byte / 1024
else
return string.format("%.2f %s", byte, suff[i])
end
end
end
function date_format(secs)
local suff = {"min", "h", "d"}
local mins = 0
local hour = 0
local days = 0
secs = math.floor(secs)
if secs > 60 then
mins = math.floor(secs / 60)
secs = secs % 60
end
if mins > 60 then
hour = math.floor(mins / 60)
mins = mins % 60
end
if hour > 24 then
days = math.floor(hour / 24)
hour = hour % 24
end
if days > 0 then
return string.format("%.0fd %02.0fh %02.0fmin %02.0fs", days, hour, mins, secs)
else
return string.format("%02.0fh %02.0fmin %02.0fs", hour, mins, secs)
end
end
function cbi_add_networks(field)
uci.cursor():foreach("network", "interface",
function (section)
if section[".name"] ~= "loopback" then
field:value(section[".name"])
end
end
)
field.titleref = luci.dispatcher.build_url("admin", "network", "network")
end
function cbi_add_knownips(field)
local _, n
for _, n in ipairs(ip.neighbors({ family = 4 })) do
if n.dest then
field:value(n.dest:string())
end
end
end
function firewall_find_zone(name)
local find
luci.model.uci.cursor():foreach("firewall", "zone",
function (section)
if section.name == name then
find = section[".name"]
end
end
)
return find
end
function iface_get_network(iface)
local link = ip.link(tostring(iface))
if link.master then
iface = link.master
end
local cur = uci.cursor()
local dump = util.ubus("network.interface", "dump", { })
if dump then
local _, net
for _, net in ipairs(dump.interface) do
if net.l3_device == iface or net.device == iface then
-- cross check with uci to filter out @name style aliases
local uciname = cur:get("network", net.interface, "ifname")
if type(uciname) == "string" and uciname:sub(1,1) ~= "@" or uciname then
return net.interface
end
end
end
end
end

739
luci-base/luasrc/util.lua Normal file
View file

@ -0,0 +1,739 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local io = require "io"
local math = require "math"
local table = require "table"
local debug = require "debug"
local ldebug = require "luci.debug"
local string = require "string"
local coroutine = require "coroutine"
local tparser = require "luci.template.parser"
local json = require "luci.jsonc"
local _ubus = require "ubus"
local _ubus_connection = nil
local getmetatable, setmetatable = getmetatable, setmetatable
local rawget, rawset, unpack = rawget, rawset, unpack
local tostring, type, assert, error = tostring, type, assert, error
local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring
local require, pcall, xpcall = require, pcall, xpcall
local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit
module "luci.util"
--
-- Pythonic string formatting extension
--
getmetatable("").__mod = function(a, b)
local ok, res
if not b then
return a
elseif type(b) == "table" then
local k, _
for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end
ok, res = pcall(a.format, a, unpack(b))
if not ok then
error(res, 2)
end
return res
else
if type(b) == "userdata" then b = tostring(b) end
ok, res = pcall(a.format, a, b)
if not ok then
error(res, 2)
end
return res
end
end
--
-- Class helper routines
--
-- Instantiates a class
local function _instantiate(class, ...)
local inst = setmetatable({}, {__index = class})
if inst.__init__ then
inst:__init__(...)
end
return inst
end
-- The class object can be instantiated by calling itself.
-- Any class functions or shared parameters can be attached to this object.
-- Attaching a table to the class object makes this table shared between
-- all instances of this class. For object parameters use the __init__ function.
-- Classes can inherit member functions and values from a base class.
-- Class can be instantiated by calling them. All parameters will be passed
-- to the __init__ function of this class - if such a function exists.
-- The __init__ function must be used to set any object parameters that are not shared
-- with other objects of this class. Any return values will be ignored.
function class(base)
return setmetatable({}, {
__call = _instantiate,
__index = base
})
end
function instanceof(object, class)
local meta = getmetatable(object)
while meta and meta.__index do
if meta.__index == class then
return true
end
meta = getmetatable(meta.__index)
end
return false
end
--
-- Scope manipulation routines
--
local tl_meta = {
__mode = "k",
__index = function(self, key)
local t = rawget(self, coxpt[coroutine.running()]
or coroutine.running() or 0)
return t and t[key]
end,
__newindex = function(self, key, value)
local c = coxpt[coroutine.running()] or coroutine.running() or 0
local r = rawget(self, c)
if not r then
rawset(self, c, { [key] = value })
else
r[key] = value
end
end
}
-- the current active coroutine. A thread local store is private a table object
-- whose values can't be accessed from outside of the running coroutine.
function threadlocal(tbl)
return setmetatable(tbl or {}, tl_meta)
end
--
-- Debugging routines
--
function perror(obj)
return io.stderr:write(tostring(obj) .. "\n")
end
function dumptable(t, maxdepth, i, seen)
i = i or 0
seen = seen or setmetatable({}, {__mode="k"})
for k,v in pairs(t) do
perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v))
if type(v) == "table" and (not maxdepth or i < maxdepth) then
if not seen[v] then
seen[v] = true
dumptable(v, maxdepth, i+1, seen)
else
perror(string.rep("\t", i) .. "*** RECURSION ***")
end
end
end
end
--
-- String and data manipulation routines
--
function pcdata(value)
return value and tparser.pcdata(tostring(value))
end
function striptags(value)
return value and tparser.striptags(tostring(value))
end
-- for bash, ash and similar shells single-quoted strings are taken
-- literally except for single quotes (which terminate the string)
-- (and the exception noted below for dash (-) at the start of a
-- command line parameter).
function shellsqescape(value)
local res
res, _ = string.gsub(value, "'", "'\\''")
return res
end
-- bash, ash and other similar shells interpret a dash (-) at the start
-- of a command-line parameters as an option indicator regardless of
-- whether it is inside a single-quoted string. It must be backlash
-- escaped to resolve this. This requires in some funky special-case
-- handling. It may actually be a property of the getopt function
-- rather than the shell proper.
function shellstartsqescape(value)
res, _ = string.gsub(value, "^\-", "\\-")
res, _ = string.gsub(res, "^-", "\-")
return shellsqescape(value)
end
-- containing the resulting substrings. The optional max parameter specifies
-- the number of bytes to process, regardless of the actual length of the given
-- string. The optional last parameter, regex, specifies whether the separator
-- sequence is interpreted as regular expression.
-- pattern as regular expression (optional, default is false)
function split(str, pat, max, regex)
pat = pat or "\n"
max = max or #str
local t = {}
local c = 1
if #str == 0 then
return {""}
end
if #pat == 0 then
return nil
end
if max == 0 then
return str
end
repeat
local s, e = str:find(pat, c, not regex)
max = max - 1
if s and max < 0 then
t[#t+1] = str:sub(c)
else
t[#t+1] = str:sub(c, s and s - 1)
end
c = e and e + 1 or #str + 1
until not s or max < 0
return t
end
function trim(str)
return (str:gsub("^%s*(.-)%s*$", "%1"))
end
function cmatch(str, pat)
local count = 0
for _ in str:gmatch(pat) do count = count + 1 end
return count
end
-- one token per invocation, the tokens are separated by whitespace. If the
-- input value is a table, it is transformed into a string first. A nil value
-- will result in a valid interator which aborts with the first invocation.
function imatch(v)
if type(v) == "table" then
local k = nil
return function()
k = next(v, k)
return v[k]
end
elseif type(v) == "number" or type(v) == "boolean" then
local x = true
return function()
if x then
x = false
return tostring(v)
end
end
elseif type(v) == "userdata" or type(v) == "string" then
return tostring(v):gmatch("%S+")
end
return function() end
end
-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
-- Recognized units are:
-- o "y" - one year (60*60*24*366)
-- o "m" - one month (60*60*24*31)
-- o "w" - one week (60*60*24*7)
-- o "d" - one day (60*60*24)
-- o "h" - one hour (60*60)
-- o "min" - one minute (60)
-- o "kb" - one kilobyte (1024)
-- o "mb" - one megabyte (1024*1024)
-- o "gb" - one gigabyte (1024*1024*1024)
-- o "kib" - one si kilobyte (1000)
-- o "mib" - one si megabyte (1000*1000)
-- o "gib" - one si gigabyte (1000*1000*1000)
function parse_units(ustr)
local val = 0
-- unit map
local map = {
-- date stuff
y = 60 * 60 * 24 * 366,
m = 60 * 60 * 24 * 31,
w = 60 * 60 * 24 * 7,
d = 60 * 60 * 24,
h = 60 * 60,
min = 60,
-- storage sizes
kb = 1024,
mb = 1024 * 1024,
gb = 1024 * 1024 * 1024,
-- storage sizes (si)
kib = 1000,
mib = 1000 * 1000,
gib = 1000 * 1000 * 1000
}
-- parse input string
for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
local num = spec:gsub("[^0-9%.]+$","")
local spn = spec:gsub("^[0-9%.]+", "")
if map[spn] or map[spn:sub(1,1)] then
val = val + num * ( map[spn] or map[spn:sub(1,1)] )
else
val = val + num
end
end
return val
end
-- also register functions above in the central string class for convenience
string.pcdata = pcdata
string.striptags = striptags
string.split = split
string.trim = trim
string.cmatch = cmatch
string.parse_units = parse_units
function append(src, ...)
for i, a in ipairs({...}) do
if type(a) == "table" then
for j, v in ipairs(a) do
src[#src+1] = v
end
else
src[#src+1] = a
end
end
return src
end
function combine(...)
return append({}, ...)
end
function contains(table, value)
for k, v in pairs(table) do
if value == v then
return k
end
end
return false
end
-- Both table are - in fact - merged together.
function update(t, updates)
for k, v in pairs(updates) do
t[k] = v
end
end
function keys(t)
local keys = { }
if t then
for k, _ in kspairs(t) do
keys[#keys+1] = k
end
end
return keys
end
function clone(object, deep)
local copy = {}
for k, v in pairs(object) do
if deep and type(v) == "table" then
v = clone(v, deep)
end
copy[k] = v
end
return setmetatable(copy, getmetatable(object))
end
function dtable()
return setmetatable({}, { __index =
function(tbl, key)
return rawget(tbl, key)
or rawget(rawset(tbl, key, dtable()), key)
end
})
end
-- Serialize the contents of a table value.
function _serialize_table(t, seen)
assert(not seen[t], "Recursion detected.")
seen[t] = true
local data = ""
local idata = ""
local ilen = 0
for k, v in pairs(t) do
if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
k = serialize_data(k, seen)
v = serialize_data(v, seen)
data = data .. ( #data > 0 and ", " or "" ) ..
'[' .. k .. '] = ' .. v
elseif k > ilen then
ilen = k
end
end
for i = 1, ilen do
local v = serialize_data(t[i], seen)
idata = idata .. ( #idata > 0 and ", " or "" ) .. v
end
return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
end
-- with loadstring().
function serialize_data(val, seen)
seen = seen or setmetatable({}, {__mode="k"})
if val == nil then
return "nil"
elseif type(val) == "number" then
return val
elseif type(val) == "string" then
return "%q" % val
elseif type(val) == "boolean" then
return val and "true" or "false"
elseif type(val) == "function" then
return "loadstring(%q)" % get_bytecode(val)
elseif type(val) == "table" then
return "{ " .. _serialize_table(val, seen) .. " }"
else
return '"[unhandled data type:' .. type(val) .. ']"'
end
end
function restore_data(str)
return loadstring("return " .. str)()
end
--
-- Byte code manipulation routines
--
-- will be stripped before it is returned.
function get_bytecode(val)
local code
if type(val) == "function" then
code = string.dump(val)
else
code = string.dump( loadstring( "return " .. serialize_data(val) ) )
end
return code -- and strip_bytecode(code)
end
-- numbers and debugging numbers will be discarded. Original version by
-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
function strip_bytecode(code)
local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
local subint
if endian == 1 then
subint = function(code, i, l)
local val = 0
for n = l, 1, -1 do
val = val * 256 + code:byte(i + n - 1)
end
return val, i + l
end
else
subint = function(code, i, l)
local val = 0
for n = 1, l, 1 do
val = val * 256 + code:byte(i + n - 1)
end
return val, i + l
end
end
local function strip_function(code)
local count, offset = subint(code, 1, size)
local stripped = { string.rep("\0", size) }
local dirty = offset + count
offset = offset + count + int * 2 + 4
offset = offset + int + subint(code, offset, int) * ins
count, offset = subint(code, offset, int)
for n = 1, count do
local t
t, offset = subint(code, offset, 1)
if t == 1 then
offset = offset + 1
elseif t == 4 then
offset = offset + size + subint(code, offset, size)
elseif t == 3 then
offset = offset + num
elseif t == 254 or t == 9 then
offset = offset + lnum
end
end
count, offset = subint(code, offset, int)
stripped[#stripped+1] = code:sub(dirty, offset - 1)
for n = 1, count do
local proto, off = strip_function(code:sub(offset, -1))
stripped[#stripped+1] = proto
offset = offset + off - 1
end
offset = offset + subint(code, offset, int) * int + int
count, offset = subint(code, offset, int)
for n = 1, count do
offset = offset + subint(code, offset, size) + size + int * 2
end
count, offset = subint(code, offset, int)
for n = 1, count do
offset = offset + subint(code, offset, size) + size
end
stripped[#stripped+1] = string.rep("\0", int * 3)
return table.concat(stripped), offset
end
return code:sub(1,12) .. strip_function(code:sub(13,-1))
end
--
-- Sorting iterator functions
--
function _sortiter( t, f )
local keys = { }
local k, v
for k, v in pairs(t) do
keys[#keys+1] = k
end
local _pos = 0
table.sort( keys, f )
return function()
_pos = _pos + 1
if _pos <= #keys then
return keys[_pos], t[keys[_pos]], _pos
end
end
end
-- the provided callback function.
function spairs(t,f)
return _sortiter( t, f )
end
-- The table pairs are sorted by key.
function kspairs(t)
return _sortiter( t )
end
-- The table pairs are sorted by value.
function vspairs(t)
return _sortiter( t, function (a,b) return t[a] < t[b] end )
end
--
-- System utility functions
--
function bigendian()
return string.byte(string.dump(function() end), 7) == 0
end
function exec(command)
local pp = io.popen(command)
local data = pp:read("*a")
pp:close()
return data
end
function execi(command)
local pp = io.popen(command)
return pp and function()
local line = pp:read()
if not line then
pp:close()
end
return line
end
end
-- Deprecated
function execl(command)
local pp = io.popen(command)
local line = ""
local data = {}
while true do
line = pp:read()
if (line == nil) then break end
data[#data+1] = line
end
pp:close()
return data
end
function ubus(object, method, data)
if not _ubus_connection then
_ubus_connection = _ubus.connect()
assert(_ubus_connection, "Unable to establish ubus connection")
end
if object and method then
if type(data) ~= "table" then
data = { }
end
return _ubus_connection:call(object, method, data)
elseif object then
return _ubus_connection:signatures(object)
else
return _ubus_connection:objects()
end
end
function serialize_json(x, cb)
local js = json.stringify(x)
if type(cb) == "function" then
cb(js)
else
return js
end
end
function libpath()
return require "nixio.fs".dirname(ldebug.__file__)
end
function checklib(fullpathexe, wantedlib)
local fs = require "nixio.fs"
local haveldd = fs.access('/usr/bin/ldd')
if not haveldd then
return false
end
local libs = exec("/usr/bin/ldd " .. fullpathexe)
if not libs then
return false
end
for k, v in ipairs(split(libs)) do
if v:find(wantedlib) then
return true
end
end
return false
end
--
-- Coroutine safe xpcall and pcall versions modified for Luci
-- original version:
-- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
--
-- Copyright © 2005 Kepler Project.
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this software and associated documentation files (the "Software"),
-- to deal in the Software without restriction, including without limitation
-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
-- and/or sell copies of the Software, and to permit persons to whom the
-- Software is furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
local performResume, handleReturnValue
local oldpcall, oldxpcall = pcall, xpcall
coxpt = {}
setmetatable(coxpt, {__mode = "kv"})
-- Identity function for copcall
local function copcall_id(trace, ...)
return ...
end
-- values of either the function or the error handler
function coxpcall(f, err, ...)
local res, co = oldpcall(coroutine.create, f)
if not res then
local params = {...}
local newf = function() return f(unpack(params)) end
co = coroutine.create(newf)
end
local c = coroutine.running()
coxpt[co] = coxpt[c] or c or 0
return performResume(err, co, ...)
end
-- values of the function or the error object
function copcall(f, ...)
return coxpcall(f, copcall_id, ...)
end
-- Handle return value of protected call
function handleReturnValue(err, co, status, ...)
if not status then
return false, err(debug.traceback(co, (...)), ...)
end
if coroutine.status(co) ~= 'suspended' then
return true, ...
end
return performResume(err, co, coroutine.yield(...))
end
-- Resume execution of protected function call
function performResume(err, co, ...)
return handleReturnValue(err, co, coroutine.resume(co, ...))
end

View file

@ -0,0 +1,378 @@
---[[
LuCI utility functions.
]]
module "luci.util"
---[[
Create a Class object (Python-style object model).
The class object can be instantiated by calling itself.
Any class functions or shared parameters can be attached to this object.
Attaching a table to the class object makes this table shared between
all instances of this class. For object parameters use the __init__ function.
Classes can inherit member functions and values from a base class.
Class can be instantiated by calling them. All parameters will be passed
to the __init__ function of this class - if such a function exists.
The __init__ function must be used to set any object parameters that are not shared
with other objects of this class. Any return values will be ignored.
@class function
@name class
@param base The base class to inherit from (optional)
@return A class object
@see instanceof
@see clone
]]
---[[
Test whether the given object is an instance of the given class.
@class function
@name instanceof
@param object Object instance
@param class Class object to test against
@return Boolean indicating whether the object is an instance
@see class
@see clone
]]
---[[
Create a new or get an already existing thread local store associated with
the current active coroutine. A thread local store is private a table object
whose values can't be accessed from outside of the running coroutine.
@class function
@name threadlocal
@return Table value representing the corresponding thread local store
]]
---[[
Write given object to stderr.
@class function
@name perror
@param obj Value to write to stderr
@return Boolean indicating whether the write operation was successful
]]
---[[
Recursively dumps a table to stdout, useful for testing and debugging.
@class function
@name dumptable
@param t Table value to dump
@param maxdepth Maximum depth
@return Always nil
]]
---[[
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
]]
---[[
Splits given string on a defined separator sequence and return a table
containing the resulting substrings. The optional max parameter specifies
the number of bytes to process, regardless of the actual length of the given
string. The optional last parameter, regex, specifies whether the separator
sequence is interpreted as regular expression.
@class function
@name split
@param str String value containing the data to split up
@param pat String with separator pattern (optional, defaults to "\n")
@param max Maximum times to split (optional)
@param regex Boolean indicating whether to interpret the separator
-- pattern as regular expression (optional, default is false)
@return Table containing the resulting substrings
]]
---[[
Remove leading and trailing whitespace from given string value.
@class function
@name trim
@param str String value containing whitespace padded data
@return String value with leading and trailing space removed
]]
---[[
Count the occurences of given substring in given string.
@class function
@name cmatch
@param str String to search in
@param pattern String containing pattern to find
@return Number of found occurences
]]
---[[
Return a matching iterator for the given value. The iterator will return
one token per invocation, the tokens are separated by whitespace. If the
input value is a table, it is transformed into a string first. A nil value
will result in a valid interator which aborts with the first invocation.
@class function
@name imatch
@param val The value to scan (table, string or nil)
@return Iterator which returns one token per call
]]
---[[
Parse certain units from the given string and return the canonical integer
value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
Recognized units are:
-- o "y" - one year (60*60*24*366)
o "m" - one month (60*60*24*31)
o "w" - one week (60*60*24*7)
o "d" - one day (60*60*24)
o "h" - one hour (60*60)
o "min" - one minute (60)
o "kb" - one kilobyte (1024)
o "mb" - one megabyte (1024*1024)
o "gb" - one gigabyte (1024*1024*1024)
o "kib" - one si kilobyte (1000)
o "mib" - one si megabyte (1000*1000)
o "gib" - one si gigabyte (1000*1000*1000)
@class function
@name parse_units
@param ustr String containing a numerical value with trailing unit
@return Number containing the canonical value
]]
---[[
Appends numerically indexed tables or single objects to a given table.
@class function
@name append
@param src Target table
@param ... Objects to insert
@return Target table
]]
---[[
Combines two or more numerically indexed tables and single objects into one table.
@class function
@name combine
@param tbl1 Table value to combine
@param tbl2 Table value to combine
@param ... More tables to combine
@return Table value containing all values of given tables
]]
---[[
Checks whether the given table contains the given value.
@class function
@name contains
@param table Table value
@param value Value to search within the given table
@return number indicating the first index at which the given value occurs
-- within table or false.
]]
---[[
Update values in given table with the values from the second given table.
Both table are - in fact - merged together.
@class function
@name update
@param t Table which should be updated
@param updates Table containing the values to update
@return Always nil
]]
---[[
Retrieve all keys of given associative table.
@class function
@name keys
@param t Table to extract keys from
@return Sorted table containing the keys
]]
---[[
Clones the given object and return it's copy.
@class function
@name clone
@param object Table value to clone
@param deep Boolean indicating whether to do recursive cloning
@return Cloned table value
]]
---[[
Create a dynamic table which automatically creates subtables.
@class function
@name dtable
@return Dynamic Table
]]
---[[
Recursively serialize given data to lua code, suitable for restoring
with loadstring().
@class function
@name serialize_data
@param val Value containing the data to serialize
@return String value containing the serialized code
@see restore_data
@see get_bytecode
]]
---[[
Restore data previously serialized with serialize_data().
@class function
@name restore_data
@param str String containing the data to restore
@return Value containing the restored data structure
@see serialize_data
@see get_bytecode
]]
---[[
Return the current runtime bytecode of the given data. The byte code
will be stripped before it is returned.
@class function
@name get_bytecode
@param val Value to return as bytecode
@return String value containing the bytecode of the given data
]]
---[[
Strips unnescessary lua bytecode from given string. Information like line
numbers and debugging numbers will be discarded. Original version by
Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
@class function
@name strip_bytecode
@param code String value containing the original lua byte code
@return String value containing the stripped lua byte code
]]
---[[
Return a key, value iterator which returns the values sorted according to
the provided callback function.
@class function
@name spairs
@param t The table to iterate
@param f A callback function to decide the order of elements
@return Function value containing the corresponding iterator
]]
---[[
Return a key, value iterator for the given table.
The table pairs are sorted by key.
@class function
@name kspairs
@param t The table to iterate
@return Function value containing the corresponding iterator
]]
---[[
Return a key, value iterator for the given table.
The table pairs are sorted by value.
@class function
@name vspairs
@param t The table to iterate
@return Function value containing the corresponding iterator
]]
---[[
Test whether the current system is operating in big endian mode.
@class function
@name bigendian
@return Boolean value indicating whether system is big endian
]]
---[[
Execute given commandline and gather stdout.
@class function
@name exec
@param command String containing command to execute
@return String containing the command's stdout
]]
---[[
Return a line-buffered iterator over the output of given command.
@class function
@name execi
@param command String containing the command to execute
@return Iterator
]]
---[[
Issue an ubus call.
@class function
@name ubus
@param object String containing the ubus object to call
@param method String containing the ubus method to call
@param values Table containing the values to pass
@return Table containin the ubus result
]]
---[[
Convert data structure to JSON
@class function
@name serialize_json
@param data The data to serialize
@param writer A function to write a chunk of JSON data (optional)
@return String containing the JSON if called without write callback
]]
---[[
Returns the absolute path to LuCI base directory.
@class function
@name libpath
@return String containing the directory path
]]
---[[
This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function
@class function
@name coxpcall
@param f Lua function to be called protected
@param err Custom error handler
@param ... Parameters passed to the function
@return A boolean whether the function call succeeded and the return
-- values of either the function or the error handler
]]
---[[
This is a coroutine-safe drop-in replacement for Lua's "pcall"-function
@class function
@name copcall
@param f Lua function to be called protected
@param ... Parameters passed to the function
@return A boolean whether the function call succeeded and the returns
-- values of the function or the error object
]]

View file

@ -0,0 +1,9 @@
-- Licensed to the public under the Apache License 2.0.
module "luci.version"
distname = "Host System"
distversion = "SDK"
luciname = "LuCI"
luciversion = "SVN"

View file

@ -0,0 +1,43 @@
<% export("cbi_apply_xhr", function(id, configs, redirect) -%>
<fieldset class="cbi-section" id="cbi-apply-<%=id%>">
<legend><%:Applying changes%></legend>
<script type="text/javascript">//<![CDATA[
var apply_xhr = new XHR();
apply_xhr.post('<%=url('servicectl/restart', table.concat(configs, ","))%>', { token: '<%=token%>' },
function() {
var checkfinish = function() {
apply_xhr.get('<%=url('servicectl/status')%>', null,
function(x) {
if( x.responseText == 'finish' )
{
var e = document.getElementById('cbi-apply-<%=id%>-status');
if( e )
{
e.innerHTML = '<%:Configuration applied.%>';
window.setTimeout(function() {
e.parentNode.style.display = 'none';
<% if redirect then %>location.href='<%=redirect%>';<% end %>
}, 1000);
}
}
else
{
var e = document.getElementById('cbi-apply-<%=id%>-status');
if( e && x.responseText ) e.innerHTML = x.responseText;
window.setTimeout(checkfinish, 1000);
}
}
);
}
window.setTimeout(checkfinish, 1000);
}
);
//]]></script>
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
<span id="cbi-apply-<%=id%>-status"><%:Waiting for changes to be applied...%></span>
</fieldset>
<%- end) %>

View file

@ -0,0 +1,7 @@
<% local v = self:cfgvalue(section) -%>
<%+cbi/valueheader%>
<input class="cbi-input-text" type="text"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> />
<script type="text/javascript">
cbi_browser_init('<%=cbid%>', '<%=resource%>', '<%=url('admin/filebrowser')%>'<%=self.default_path and ", '"..self.default_path.."'"%>);
</script>
<%+cbi/valuefooter%>

View file

@ -0,0 +1,7 @@
<%+cbi/valueheader%>
<% if self:cfgvalue(section) ~= false then %>
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
<% else %>
-
<% end %>
<%+cbi/valuefooter%>

View file

@ -0,0 +1,2 @@
</div>
</td>

Some files were not shown because too many files have changed in this diff Show more