Add macvlan support
48
luci-base/Makefile
Normal 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
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/lua
|
||||
require "luci.cacheloader"
|
||||
require "luci.sgi.cgi"
|
||||
luci.dispatcher.indexcache = "/tmp/luci-indexcache"
|
||||
luci.sgi.cgi.run()
|
1540
luci-base/htdocs/luci-static/resources/cbi.js
Normal file
BIN
luci-base/htdocs/luci-static/resources/cbi/add.gif
Normal file
After Width: | Height: | Size: 378 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/apply.gif
Normal file
After Width: | Height: | Size: 268 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/arrow.gif
Normal file
After Width: | Height: | Size: 135 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/down.gif
Normal file
After Width: | Height: | Size: 131 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/download.gif
Normal file
After Width: | Height: | Size: 189 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/edit.gif
Normal file
After Width: | Height: | Size: 272 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/fieldadd.gif
Normal file
After Width: | Height: | Size: 371 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/file.gif
Normal file
After Width: | Height: | Size: 267 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/find.gif
Normal file
After Width: | Height: | Size: 273 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/folder.gif
Normal file
After Width: | Height: | Size: 698 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/help.gif
Normal file
After Width: | Height: | Size: 266 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/key.gif
Normal file
After Width: | Height: | Size: 230 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/link.gif
Normal file
After Width: | Height: | Size: 279 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/reload.gif
Normal file
After Width: | Height: | Size: 248 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/remove.gif
Normal file
After Width: | Height: | Size: 385 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/reset.gif
Normal file
After Width: | Height: | Size: 258 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/save.gif
Normal file
After Width: | Height: | Size: 263 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/up.gif
Normal file
After Width: | Height: | Size: 130 B |
BIN
luci-base/htdocs/luci-static/resources/cbi/user.gif
Normal file
After Width: | Height: | Size: 246 B |
BIN
luci-base/htdocs/luci-static/resources/icons/bridge.png
Normal file
After Width: | Height: | Size: 681 B |
BIN
luci-base/htdocs/luci-static/resources/icons/bridge_disabled.png
Normal file
After Width: | Height: | Size: 405 B |
BIN
luci-base/htdocs/luci-static/resources/icons/encryption.png
Normal file
After Width: | Height: | Size: 920 B |
After Width: | Height: | Size: 888 B |
BIN
luci-base/htdocs/luci-static/resources/icons/ethernet.png
Normal file
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 399 B |
BIN
luci-base/htdocs/luci-static/resources/icons/loading.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
luci-base/htdocs/luci-static/resources/icons/port_down.png
Normal file
After Width: | Height: | Size: 769 B |
BIN
luci-base/htdocs/luci-static/resources/icons/port_up.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
luci-base/htdocs/luci-static/resources/icons/signal-0-25.png
Normal file
After Width: | Height: | Size: 462 B |
BIN
luci-base/htdocs/luci-static/resources/icons/signal-0.png
Normal file
After Width: | Height: | Size: 439 B |
BIN
luci-base/htdocs/luci-static/resources/icons/signal-25-50.png
Normal file
After Width: | Height: | Size: 465 B |
BIN
luci-base/htdocs/luci-static/resources/icons/signal-50-75.png
Normal file
After Width: | Height: | Size: 467 B |
BIN
luci-base/htdocs/luci-static/resources/icons/signal-75-100.png
Normal file
After Width: | Height: | Size: 457 B |
BIN
luci-base/htdocs/luci-static/resources/icons/signal-none.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
luci-base/htdocs/luci-static/resources/icons/switch.png
Normal file
After Width: | Height: | Size: 680 B |
BIN
luci-base/htdocs/luci-static/resources/icons/switch_disabled.png
Normal file
After Width: | Height: | Size: 398 B |
BIN
luci-base/htdocs/luci-static/resources/icons/tunnel.png
Normal file
After Width: | Height: | Size: 343 B |
BIN
luci-base/htdocs/luci-static/resources/icons/tunnel_disabled.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
luci-base/htdocs/luci-static/resources/icons/vlan.png
Normal file
After Width: | Height: | Size: 680 B |
BIN
luci-base/htdocs/luci-static/resources/icons/vlan_disabled.png
Normal file
After Width: | Height: | Size: 398 B |
BIN
luci-base/htdocs/luci-static/resources/icons/wifi.png
Normal file
After Width: | Height: | Size: 767 B |
BIN
luci-base/htdocs/luci-static/resources/icons/wifi_big.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
BIN
luci-base/htdocs/luci-static/resources/icons/wifi_disabled.png
Normal file
After Width: | Height: | Size: 494 B |
241
luci-base/htdocs/luci-static/resources/xhr.js
Normal 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);
|
||||
}
|
12
luci-base/luasrc/cacheloader.lua
Normal 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
471
luci-base/luasrc/cbi/datatypes.lua
Normal 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
|
||||
|
76
luci-base/luasrc/ccache.lua
Normal 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
|
18
luci-base/luasrc/config.lua
Normal 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)
|
49
luci-base/luasrc/controller/admin/servicectl.lua
Normal 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
|
37
luci-base/luasrc/debug.lua
Normal 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
|
||||
|
918
luci-base/luasrc/dispatcher.lua
Normal 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
|
220
luci-base/luasrc/dispatcher.luadoc
Normal 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
|
@ -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
|
165
luci-base/luasrc/http.luadoc
Normal 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
|
||||
]]
|
||||
|
649
luci-base/luasrc/http/protocol.lua
Normal 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",
|
||||
}
|
142
luci-base/luasrc/http/protocol.luadoc
Normal 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
|
||||
]]
|
||||
|
110
luci-base/luasrc/http/protocol/conditionals.lua
Normal 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
|
85
luci-base/luasrc/http/protocol/conditionals.luadoc
Normal 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
|
||||
]]
|
||||
|
87
luci-base/luasrc/http/protocol/date.lua
Normal 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
|
46
luci-base/luasrc/http/protocol/date.luadoc
Normal 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
|
||||
]]
|
||||
|
78
luci-base/luasrc/http/protocol/mime.lua
Normal 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
|
34
luci-base/luasrc/http/protocol/mime.luadoc
Normal 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
|
@ -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
|
84
luci-base/luasrc/i18n.luadoc
Normal 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
|
@ -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
|
||||
|
67
luci-base/luasrc/model/cbi/admin_network/proto_dhcp.lua
Normal 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)"
|
4
luci-base/luasrc/model/cbi/admin_network/proto_none.lua
Normal 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 = ...
|
97
luci-base/luasrc/model/cbi/admin_network/proto_static.lua
Normal 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
|
566
luci-base/luasrc/model/firewall.lua
Normal 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
|
242
luci-base/luasrc/model/ipkg.lua
Normal 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
|
125
luci-base/luasrc/model/ipkg.luadoc
Normal 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
|
||||
]]
|
||||
|
1707
luci-base/luasrc/model/network.lua
Normal file
236
luci-base/luasrc/model/uci.lua
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
299
luci-base/luasrc/model/uci.luadoc
Normal 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
|
||||
]]
|
||||
|
73
luci-base/luasrc/sgi/cgi.lua
Normal 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
|
89
luci-base/luasrc/sgi/uhttpd.lua
Normal 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
|
6
luci-base/luasrc/store.lua
Normal 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
|
@ -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
|
@ -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
|
||||
]]
|
||||
|
374
luci-base/luasrc/sys/iptparser.lua
Normal 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
|
69
luci-base/luasrc/sys/iptparser.luadoc
Normal 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.
|
||||
]]
|
||||
|
19
luci-base/luasrc/sys/zoneinfo.lua
Normal 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
|
||||
})
|
457
luci-base/luasrc/sys/zoneinfo/tzdata.lua
Normal 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' },
|
||||
}
|
45
luci-base/luasrc/sys/zoneinfo/tzoffset.lua
Normal 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
|
||||
}
|
100
luci-base/luasrc/template.lua
Normal 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
|
36
luci-base/luasrc/tools/proto.lua
Normal 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
|
230
luci-base/luasrc/tools/status.lua
Normal 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
|
105
luci-base/luasrc/tools/webadmin.lua
Normal 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
|
@ -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
|
378
luci-base/luasrc/util.luadoc
Normal 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
|
||||
]]
|
||||
|
9
luci-base/luasrc/version.lua
Normal 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"
|
43
luci-base/luasrc/view/cbi/apply_xhr.htm
Normal 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) %>
|
7
luci-base/luasrc/view/cbi/browser.htm
Normal 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%>
|
7
luci-base/luasrc/view/cbi/button.htm
Normal 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%>
|
2
luci-base/luasrc/view/cbi/cell_valuefooter.htm
Normal file
|
@ -0,0 +1,2 @@
|
|||
</div>
|
||||
</td>
|