From: Jo-Philipp Wich Date: Wed, 11 Jun 2014 14:00:09 +0000 (+0000) Subject: Fix wrong directory in luci-base X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=d4dc08dcb219a278a94e68aa87c13bca6361eeca;p=project%2Fluci.git Fix wrong directory in luci-base --- diff --git a/modules/base/luasrc/cacheloader.lua b/modules/base/luasrc/cacheloader.lua new file mode 100644 index 0000000000..942c4b7b48 --- /dev/null +++ b/modules/base/luasrc/cacheloader.lua @@ -0,0 +1,23 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +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 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 \ No newline at end of file diff --git a/modules/base/luasrc/cbi.lua b/modules/base/luasrc/cbi.lua new file mode 100644 index 0000000000..ae570b1556 --- /dev/null +++ b/modules/base/luasrc/cbi.lua @@ -0,0 +1,1850 @@ +--[[ +LuCI - Configuration Bind Interface + +Description: +Offers an interface for binding configuration values to certain +data types. Supports value and range validation and basic dependencies. + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- +module("luci.cbi", package.seeall) + +require("luci.template") +local util = require("luci.util") +require("luci.http") + + +--local event = require "luci.sys.event" +local fs = require("nixio.fs") +local uci = require("luci.model.uci") +local datatypes = require("luci.cbi.datatypes") +local class = util.class +local instanceof = util.instanceof + +FORM_NODATA = 0 +FORM_PROCEED = 0 +FORM_VALID = 1 +FORM_DONE = 1 +FORM_INVALID = -1 +FORM_CHANGED = 2 +FORM_SKIP = 4 + +AUTO = true + +CREATE_PREFIX = "cbi.cts." +REMOVE_PREFIX = "cbi.rts." +RESORT_PREFIX = "cbi.sts." +FEXIST_PREFIX = "cbi.cbe." + +-- Loads a CBI map from given file, creating an environment and returns it +function load(cbimap, ...) + local fs = require "nixio.fs" + local i18n = require "luci.i18n" + require("luci.config") + require("luci.util") + + local upldir = "/lib/uci/upload/" + local cbidir = luci.util.libpath() .. "/model/cbi/" + local func, err + + if fs.access(cbidir..cbimap..".lua") then + func, err = loadfile(cbidir..cbimap..".lua") + elseif fs.access(cbimap) then + func, err = loadfile(cbimap) + else + func, err = nil, "Model '" .. cbimap .. "' not found!" + end + + assert(func, err) + + local env = { + translate=i18n.translate, + translatef=i18n.translatef, + arg={...} + } + + setfenv(func, setmetatable(env, {__index = + function(tbl, key) + return rawget(tbl, key) or _M[key] or _G[key] + end})) + + local maps = { func() } + local uploads = { } + local has_upload = false + + for i, map in ipairs(maps) do + if not instanceof(map, Node) then + error("CBI map returns no valid map object!") + return nil + else + map:prepare() + if map.upload_fields then + has_upload = true + for _, field in ipairs(map.upload_fields) do + uploads[ + field.config .. '.' .. + (field.section.sectiontype or '1') .. '.' .. + field.option + ] = true + end + end + end + end + + if has_upload then + local uci = luci.model.uci.cursor() + local prm = luci.http.context.request.message.params + local fd, cbid + + luci.http.setfilehandler( + function( field, chunk, eof ) + if not field then return end + if field.name and not cbid then + local c, s, o = field.name:gmatch( + "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)" + )() + + if c and s and o then + local t = uci:get( c, s ) or s + if uploads[c.."."..t.."."..o] then + local path = upldir .. field.name + fd = io.open(path, "w") + if fd then + cbid = field.name + prm[cbid] = path + end + end + end + end + + if field.name == cbid and fd then + fd:write(chunk) + end + + if eof and fd then + fd:close() + fd = nil + cbid = nil + end + end + ) + end + + return maps +end + +-- +-- Compile a datatype specification into a parse tree for evaluation later on +-- +local cdt_cache = { } + +function compile_datatype(code) + local i + local pos = 0 + local esc = false + local depth = 0 + local stack = { } + + for i = 1, #code+1 do + local byte = code:byte(i) or 44 + if esc then + esc = false + elseif byte == 92 then + esc = true + elseif byte == 40 or byte == 44 then + if depth <= 0 then + if pos < i then + local label = code:sub(pos, i-1) + :gsub("\\(.)", "%1") + :gsub("^%s+", "") + :gsub("%s+$", "") + + if #label > 0 and tonumber(label) then + stack[#stack+1] = tonumber(label) + elseif label:match("^'.*'$") or label:match('^".*"$') then + stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1") + elseif type(datatypes[label]) == "function" then + stack[#stack+1] = datatypes[label] + stack[#stack+1] = { } + else + error("Datatype error, bad token %q" % label) + end + end + pos = i + 1 + end + depth = depth + (byte == 40 and 1 or 0) + elseif byte == 41 then + depth = depth - 1 + if depth <= 0 then + if type(stack[#stack-1]) ~= "function" then + error("Datatype error, argument list follows non-function") + end + stack[#stack] = compile_datatype(code:sub(pos, i-1)) + pos = i + 1 + end + end + end + + return stack +end + +function verify_datatype(dt, value) + if dt and #dt > 0 then + if not cdt_cache[dt] then + local c = compile_datatype(dt) + if c and type(c[1]) == "function" then + cdt_cache[dt] = c + else + error("Datatype error, not a function expression") + end + end + if cdt_cache[dt] then + return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2])) + end + end + return true +end + + +-- Node pseudo abstract class +Node = class() + +function Node.__init__(self, title, description) + self.children = {} + self.title = title or "" + self.description = description or "" + self.template = "cbi/node" +end + +-- hook helper +function Node._run_hook(self, hook) + if type(self[hook]) == "function" then + return self[hook](self) + end +end + +function Node._run_hooks(self, ...) + local f + local r = false + for _, f in ipairs(arg) do + if type(self[f]) == "function" then + self[f](self) + r = true + end + end + return r +end + +-- Prepare nodes +function Node.prepare(self, ...) + for k, child in ipairs(self.children) do + child:prepare(...) + end +end + +-- Append child nodes +function Node.append(self, obj) + table.insert(self.children, obj) +end + +-- Parse this node and its children +function Node.parse(self, ...) + for k, child in ipairs(self.children) do + child:parse(...) + end +end + +-- Render this node +function Node.render(self, scope) + scope = scope or {} + scope.self = self + + luci.template.render(self.template, scope) +end + +-- Render the children +function Node.render_children(self, ...) + local k, node + for k, node in ipairs(self.children) do + node.last_child = (k == #self.children) + node:render(...) + end +end + + +--[[ +A simple template element +]]-- +Template = class(Node) + +function Template.__init__(self, template) + Node.__init__(self) + self.template = template +end + +function Template.render(self) + luci.template.render(self.template, {self=self}) +end + +function Template.parse(self, readinput) + self.readinput = (readinput ~= false) + return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA +end + + +--[[ +Map - A map describing a configuration file +]]-- +Map = class(Node) + +function Map.__init__(self, config, ...) + Node.__init__(self, ...) + + self.config = config + self.parsechain = {self.config} + self.template = "cbi/map" + self.apply_on_parse = nil + self.readinput = true + self.proceed = false + self.flow = {} + + self.uci = uci.cursor() + self.save = true + + self.changed = false + + if not self.uci:load(self.config) then + error("Unable to read UCI data: " .. self.config) + end +end + +function Map.formvalue(self, key) + return self.readinput and luci.http.formvalue(key) +end + +function Map.formvaluetable(self, key) + return self.readinput and luci.http.formvaluetable(key) or {} +end + +function Map.get_scheme(self, sectiontype, option) + if not option then + return self.scheme and self.scheme.sections[sectiontype] + else + return self.scheme and self.scheme.variables[sectiontype] + and self.scheme.variables[sectiontype][option] + end +end + +function Map.submitstate(self) + return self:formvalue("cbi.submit") +end + +-- Chain foreign config +function Map.chain(self, config) + table.insert(self.parsechain, config) +end + +function Map.state_handler(self, state) + return state +end + +-- Use optimized UCI writing +function Map.parse(self, readinput, ...) + self.readinput = (readinput ~= false) + self:_run_hooks("on_parse") + + if self:formvalue("cbi.skip") then + self.state = FORM_SKIP + return self:state_handler(self.state) + end + + Node.parse(self, ...) + + if self.save then + self:_run_hooks("on_save", "on_before_save") + for i, config in ipairs(self.parsechain) do + self.uci:save(config) + end + self:_run_hooks("on_after_save") + if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then + self:_run_hooks("on_before_commit") + for i, config in ipairs(self.parsechain) do + self.uci:commit(config) + + -- Refresh data because commit changes section names + self.uci:load(config) + end + self:_run_hooks("on_commit", "on_after_commit", "on_before_apply") + if self.apply_on_parse then + self.uci:apply(self.parsechain) + self:_run_hooks("on_apply", "on_after_apply") + else + -- This is evaluated by the dispatcher and delegated to the + -- template which in turn fires XHR to perform the actual + -- apply actions. + self.apply_needed = true + end + + -- Reparse sections + Node.parse(self, true) + + end + for i, config in ipairs(self.parsechain) do + self.uci:unload(config) + end + if type(self.commit_handler) == "function" then + self:commit_handler(self:submitstate()) + end + end + + if self:submitstate() then + if not self.save then + self.state = FORM_INVALID + elseif self.proceed then + self.state = FORM_PROCEED + else + self.state = self.changed and FORM_CHANGED or FORM_VALID + end + else + self.state = FORM_NODATA + end + + return self:state_handler(self.state) +end + +function Map.render(self, ...) + self:_run_hooks("on_init") + Node.render(self, ...) +end + +-- Creates a child section +function Map.section(self, class, ...) + if instanceof(class, AbstractSection) then + local obj = class(self, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractSection") + end +end + +-- UCI add +function Map.add(self, sectiontype) + return self.uci:add(self.config, sectiontype) +end + +-- UCI set +function Map.set(self, section, option, value) + if type(value) ~= "table" or #value > 0 then + if option then + return self.uci:set(self.config, section, option, value) + else + return self.uci:set(self.config, section, value) + end + else + return Map.del(self, section, option) + end +end + +-- UCI del +function Map.del(self, section, option) + if option then + return self.uci:delete(self.config, section, option) + else + return self.uci:delete(self.config, section) + end +end + +-- UCI get +function Map.get(self, section, option) + if not section then + return self.uci:get_all(self.config) + elseif option then + return self.uci:get(self.config, section, option) + else + return self.uci:get_all(self.config, section) + end +end + +--[[ +Compound - Container +]]-- +Compound = class(Node) + +function Compound.__init__(self, ...) + Node.__init__(self) + self.template = "cbi/compound" + self.children = {...} +end + +function Compound.populate_delegator(self, delegator) + for _, v in ipairs(self.children) do + v.delegator = delegator + end +end + +function Compound.parse(self, ...) + local cstate, state = 0 + + for k, child in ipairs(self.children) do + cstate = child:parse(...) + state = (not state or cstate < state) and cstate or state + end + + return state +end + + +--[[ +Delegator - Node controller +]]-- +Delegator = class(Node) +function Delegator.__init__(self, ...) + Node.__init__(self, ...) + self.nodes = {} + self.defaultpath = {} + self.pageaction = false + self.readinput = true + self.allow_reset = false + self.allow_cancel = false + self.allow_back = false + self.allow_finish = false + self.template = "cbi/delegator" +end + +function Delegator.set(self, name, node) + assert(not self.nodes[name], "Duplicate entry") + + self.nodes[name] = node +end + +function Delegator.add(self, name, node) + node = self:set(name, node) + self.defaultpath[#self.defaultpath+1] = name +end + +function Delegator.insert_after(self, name, after) + local n = #self.chain + 1 + for k, v in ipairs(self.chain) do + if v == after then + n = k + 1 + break + end + end + table.insert(self.chain, n, name) +end + +function Delegator.set_route(self, ...) + local n, chain, route = 0, self.chain, {...} + for i = 1, #chain do + if chain[i] == self.current then + n = i + break + end + end + for i = 1, #route do + n = n + 1 + chain[n] = route[i] + end + for i = n + 1, #chain do + chain[i] = nil + end +end + +function Delegator.get(self, name) + local node = self.nodes[name] + + if type(node) == "string" then + node = load(node, name) + end + + if type(node) == "table" and getmetatable(node) == nil then + node = Compound(unpack(node)) + end + + return node +end + +function Delegator.parse(self, ...) + if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then + if self:_run_hooks("on_cancel") then + return FORM_DONE + end + end + + if not Map.formvalue(self, "cbi.delg.current") then + self:_run_hooks("on_init") + end + + local newcurrent + self.chain = self.chain or self:get_chain() + self.current = self.current or self:get_active() + self.active = self.active or self:get(self.current) + assert(self.active, "Invalid state") + + local stat = FORM_DONE + if type(self.active) ~= "function" then + self.active:populate_delegator(self) + stat = self.active:parse() + else + self:active() + end + + if stat > FORM_PROCEED then + if Map.formvalue(self, "cbi.delg.back") then + newcurrent = self:get_prev(self.current) + else + newcurrent = self:get_next(self.current) + end + elseif stat < FORM_PROCEED then + return stat + end + + + if not Map.formvalue(self, "cbi.submit") then + return FORM_NODATA + elseif stat > FORM_PROCEED + and (not newcurrent or not self:get(newcurrent)) then + return self:_run_hook("on_done") or FORM_DONE + else + self.current = newcurrent or self.current + self.active = self:get(self.current) + if type(self.active) ~= "function" then + self.active:populate_delegator(self) + local stat = self.active:parse(false) + if stat == FORM_SKIP then + return self:parse(...) + else + return FORM_PROCEED + end + else + return self:parse(...) + end + end +end + +function Delegator.get_next(self, state) + for k, v in ipairs(self.chain) do + if v == state then + return self.chain[k+1] + end + end +end + +function Delegator.get_prev(self, state) + for k, v in ipairs(self.chain) do + if v == state then + return self.chain[k-1] + end + end +end + +function Delegator.get_chain(self) + local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath + return type(x) == "table" and x or {x} +end + +function Delegator.get_active(self) + return Map.formvalue(self, "cbi.delg.current") or self.chain[1] +end + +--[[ +Page - A simple node +]]-- + +Page = class(Node) +Page.__init__ = Node.__init__ +Page.parse = function() end + + +--[[ +SimpleForm - A Simple non-UCI form +]]-- +SimpleForm = class(Node) + +function SimpleForm.__init__(self, config, title, description, data) + Node.__init__(self, title, description) + self.config = config + self.data = data or {} + self.template = "cbi/simpleform" + self.dorender = true + self.pageaction = false + self.readinput = true +end + +SimpleForm.formvalue = Map.formvalue +SimpleForm.formvaluetable = Map.formvaluetable + +function SimpleForm.parse(self, readinput, ...) + self.readinput = (readinput ~= false) + + if self:formvalue("cbi.skip") then + return FORM_SKIP + end + + if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then + return FORM_DONE + end + + if self:submitstate() then + Node.parse(self, 1, ...) + end + + local valid = true + for k, j in ipairs(self.children) do + for i, v in ipairs(j.children) do + valid = valid + and (not v.tag_missing or not v.tag_missing[1]) + and (not v.tag_invalid or not v.tag_invalid[1]) + and (not v.error) + end + end + + local state = + not self:submitstate() and FORM_NODATA + or valid and FORM_VALID + or FORM_INVALID + + self.dorender = not self.handle + if self.handle then + local nrender, nstate = self:handle(state, self.data) + self.dorender = self.dorender or (nrender ~= false) + state = nstate or state + end + return state +end + +function SimpleForm.render(self, ...) + if self.dorender then + Node.render(self, ...) + end +end + +function SimpleForm.submitstate(self) + return self:formvalue("cbi.submit") +end + +function SimpleForm.section(self, class, ...) + if instanceof(class, AbstractSection) then + local obj = class(self, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractSection") + end +end + +-- Creates a child field +function SimpleForm.field(self, class, ...) + local section + for k, v in ipairs(self.children) do + if instanceof(v, SimpleSection) then + section = v + break + end + end + if not section then + section = self:section(SimpleSection) + end + + if instanceof(class, AbstractValue) then + local obj = class(self, section, ...) + obj.track_missing = true + section:append(obj) + return obj + else + error("class must be a descendent of AbstractValue") + end +end + +function SimpleForm.set(self, section, option, value) + self.data[option] = value +end + + +function SimpleForm.del(self, section, option) + self.data[option] = nil +end + + +function SimpleForm.get(self, section, option) + return self.data[option] +end + + +function SimpleForm.get_scheme() + return nil +end + + +Form = class(SimpleForm) + +function Form.__init__(self, ...) + SimpleForm.__init__(self, ...) + self.embedded = true +end + + +--[[ +AbstractSection +]]-- +AbstractSection = class(Node) + +function AbstractSection.__init__(self, map, sectiontype, ...) + Node.__init__(self, ...) + self.sectiontype = sectiontype + self.map = map + self.config = map.config + self.optionals = {} + self.defaults = {} + self.fields = {} + self.tag_error = {} + self.tag_invalid = {} + self.tag_deperror = {} + self.changed = false + + self.optional = true + self.addremove = false + self.dynamic = false +end + +-- Define a tab for the section +function AbstractSection.tab(self, tab, title, desc) + self.tabs = self.tabs or { } + self.tab_names = self.tab_names or { } + + self.tab_names[#self.tab_names+1] = tab + self.tabs[tab] = { + title = title, + description = desc, + childs = { } + } +end + +-- Check whether the section has tabs +function AbstractSection.has_tabs(self) + return (self.tabs ~= nil) and (next(self.tabs) ~= nil) +end + +-- Appends a new option +function AbstractSection.option(self, class, option, ...) + if instanceof(class, AbstractValue) then + local obj = class(self.map, self, option, ...) + self:append(obj) + self.fields[option] = obj + return obj + elseif class == true then + error("No valid class was given and autodetection failed.") + else + error("class must be a descendant of AbstractValue") + end +end + +-- Appends a new tabbed option +function AbstractSection.taboption(self, tab, ...) + + assert(tab and self.tabs and self.tabs[tab], + "Cannot assign option to not existing tab %q" % tostring(tab)) + + local l = self.tabs[tab].childs + local o = AbstractSection.option(self, ...) + + if o then l[#l+1] = o end + + return o +end + +-- Render a single tab +function AbstractSection.render_tab(self, tab, ...) + + assert(tab and self.tabs and self.tabs[tab], + "Cannot render not existing tab %q" % tostring(tab)) + + local k, node + for k, node in ipairs(self.tabs[tab].childs) do + node.last_child = (k == #self.tabs[tab].childs) + node:render(...) + end +end + +-- Parse optional options +function AbstractSection.parse_optionals(self, section) + if not self.optional then + return + end + + self.optionals[section] = {} + + local field = self.map:formvalue("cbi.opt."..self.config.."."..section) + for k,v in ipairs(self.children) do + if v.optional and not v:cfgvalue(section) and not self:has_tabs() then + if field == v.option then + field = nil + self.map.proceed = true + else + table.insert(self.optionals[section], v) + end + end + end + + if field and #field > 0 and self.dynamic then + self:add_dynamic(field) + end +end + +-- Add a dynamic option +function AbstractSection.add_dynamic(self, field, optional) + local o = self:option(Value, field, field) + o.optional = optional +end + +-- Parse all dynamic options +function AbstractSection.parse_dynamic(self, section) + if not self.dynamic then + return + end + + local arr = luci.util.clone(self:cfgvalue(section)) + local form = self.map:formvaluetable("cbid."..self.config.."."..section) + for k, v in pairs(form) do + arr[k] = v + end + + for key,val in pairs(arr) do + local create = true + + for i,c in ipairs(self.children) do + if c.option == key then + create = false + end + end + + if create and key:sub(1, 1) ~= "." then + self.map.proceed = true + self:add_dynamic(key, true) + end + end +end + +-- Returns the section's UCI table +function AbstractSection.cfgvalue(self, section) + return self.map:get(section) +end + +-- Push events +function AbstractSection.push_events(self) + --luci.util.append(self.map.events, self.events) + self.map.changed = true +end + +-- Removes the section +function AbstractSection.remove(self, section) + self.map.proceed = true + return self.map:del(section) +end + +-- Creates the section +function AbstractSection.create(self, section) + local stat + + if section then + stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype) + else + section = self.map:add(self.sectiontype) + stat = section + end + + if stat then + for k,v in pairs(self.children) do + if v.default then + self.map:set(section, v.option, v.default) + end + end + + for k,v in pairs(self.defaults) do + self.map:set(section, k, v) + end + end + + self.map.proceed = true + + return stat +end + + +SimpleSection = class(AbstractSection) + +function SimpleSection.__init__(self, form, ...) + AbstractSection.__init__(self, form, nil, ...) + self.template = "cbi/nullsection" +end + + +Table = class(AbstractSection) + +function Table.__init__(self, form, data, ...) + local datasource = {} + local tself = self + datasource.config = "table" + self.data = data or {} + + datasource.formvalue = Map.formvalue + datasource.formvaluetable = Map.formvaluetable + datasource.readinput = true + + function datasource.get(self, section, option) + return tself.data[section] and tself.data[section][option] + end + + function datasource.submitstate(self) + return Map.formvalue(self, "cbi.submit") + end + + function datasource.del(...) + return true + end + + function datasource.get_scheme() + return nil + end + + AbstractSection.__init__(self, datasource, "table", ...) + self.template = "cbi/tblsection" + self.rowcolors = true + self.anonymous = true +end + +function Table.parse(self, readinput) + self.map.readinput = (readinput ~= false) + for i, k in ipairs(self:cfgsections()) do + if self.map:submitstate() then + Node.parse(self, k) + end + end +end + +function Table.cfgsections(self) + local sections = {} + + for i, v in luci.util.kspairs(self.data) do + table.insert(sections, i) + end + + return sections +end + +function Table.update(self, data) + self.data = data +end + + + +--[[ +NamedSection - A fixed configuration section defined by its name +]]-- +NamedSection = class(AbstractSection) + +function NamedSection.__init__(self, map, section, stype, ...) + AbstractSection.__init__(self, map, stype, ...) + + -- Defaults + self.addremove = false + self.template = "cbi/nsection" + self.section = section +end + +function NamedSection.parse(self, novld) + local s = self.section + local active = self:cfgvalue(s) + + if self.addremove then + local path = self.config.."."..s + if active then -- Remove the section + if self.map:formvalue("cbi.rns."..path) and self:remove(s) then + self:push_events() + return + end + else -- Create and apply default values + if self.map:formvalue("cbi.cns."..path) then + self:create(s) + return + end + end + end + + if active then + AbstractSection.parse_dynamic(self, s) + if self.map:submitstate() then + Node.parse(self, s) + end + AbstractSection.parse_optionals(self, s) + + if self.changed then + self:push_events() + end + end +end + + +--[[ +TypedSection - A (set of) configuration section(s) defined by the type + addremove: Defines whether the user can add/remove sections of this type + anonymous: Allow creating anonymous sections + validate: a validation function returning nil if the section is invalid +]]-- +TypedSection = class(AbstractSection) + +function TypedSection.__init__(self, map, type, ...) + AbstractSection.__init__(self, map, type, ...) + + self.template = "cbi/tsection" + self.deps = {} + self.anonymous = false +end + +-- Return all matching UCI sections for this TypedSection +function TypedSection.cfgsections(self) + local sections = {} + self.map.uci:foreach(self.map.config, self.sectiontype, + function (section) + if self:checkscope(section[".name"]) then + table.insert(sections, section[".name"]) + end + end) + + return sections +end + +-- Limits scope to sections that have certain option => value pairs +function TypedSection.depends(self, option, value) + table.insert(self.deps, {option=option, value=value}) +end + +function TypedSection.parse(self, novld) + if self.addremove then + -- Remove + local crval = REMOVE_PREFIX .. self.config + local name = self.map:formvaluetable(crval) + for k,v in pairs(name) do + if k:sub(-2) == ".x" then + k = k:sub(1, #k - 2) + end + if self:cfgvalue(k) and self:checkscope(k) then + self:remove(k) + end + end + end + + local co + for i, k in ipairs(self:cfgsections()) do + AbstractSection.parse_dynamic(self, k) + if self.map:submitstate() then + Node.parse(self, k, novld) + end + AbstractSection.parse_optionals(self, k) + end + + if self.addremove then + -- Create + local created + local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype + local origin, name = next(self.map:formvaluetable(crval)) + if self.anonymous then + if name then + created = self:create(nil, origin) + end + else + if name then + -- Ignore if it already exists + if self:cfgvalue(name) then + name = nil; + end + + name = self:checkscope(name) + + if not name then + self.err_invalid = true + end + + if name and #name > 0 then + created = self:create(name, origin) and name + if not created then + self.invalid_cts = true + end + end + end + end + + if created then + AbstractSection.parse_optionals(self, created) + end + end + + if self.sortable then + local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype + local order = self.map:formvalue(stval) + if order and #order > 0 then + local sid + local num = 0 + for sid in util.imatch(order) do + self.map.uci:reorder(self.config, sid, num) + num = num + 1 + end + self.changed = (num > 0) + end + end + + if created or self.changed then + self:push_events() + end +end + +-- Verifies scope of sections +function TypedSection.checkscope(self, section) + -- Check if we are not excluded + if self.filter and not self:filter(section) then + return nil + end + + -- Check if at least one dependency is met + if #self.deps > 0 and self:cfgvalue(section) then + local stat = false + + for k, v in ipairs(self.deps) do + if self:cfgvalue(section)[v.option] == v.value then + stat = true + end + end + + if not stat then + return nil + end + end + + return self:validate(section) +end + + +-- Dummy validate function +function TypedSection.validate(self, section) + return section +end + + +--[[ +AbstractValue - An abstract Value Type + null: Value can be empty + valid: A function returning the value if it is valid otherwise nil + depends: A table of option => value pairs of which one must be true + default: The default value + size: The size of the input fields + rmempty: Unset value if empty + optional: This value is optional (see AbstractSection.optionals) +]]-- +AbstractValue = class(Node) + +function AbstractValue.__init__(self, map, section, option, ...) + Node.__init__(self, ...) + self.section = section + self.option = option + self.map = map + self.config = map.config + self.tag_invalid = {} + self.tag_missing = {} + self.tag_reqerror = {} + self.tag_error = {} + self.deps = {} + self.subdeps = {} + --self.cast = "string" + + self.track_missing = false + self.rmempty = true + self.default = nil + self.size = nil + self.optional = false +end + +function AbstractValue.prepare(self) + self.cast = self.cast or "string" +end + +-- Add a dependencie to another section field +function AbstractValue.depends(self, field, value) + local deps + if type(field) == "string" then + deps = {} + deps[field] = value + else + deps = field + end + + table.insert(self.deps, {deps=deps, add=""}) +end + +-- Generates the unique CBID +function AbstractValue.cbid(self, section) + return "cbid."..self.map.config.."."..section.."."..self.option +end + +-- Return whether this object should be created +function AbstractValue.formcreated(self, section) + local key = "cbi.opt."..self.config.."."..section + return (self.map:formvalue(key) == self.option) +end + +-- Returns the formvalue for this object +function AbstractValue.formvalue(self, section) + return self.map:formvalue(self:cbid(section)) +end + +function AbstractValue.additional(self, value) + self.optional = value +end + +function AbstractValue.mandatory(self, value) + self.rmempty = not value +end + +function AbstractValue.add_error(self, section, type, msg) + self.error = self.error or { } + self.error[section] = msg or type + + self.section.error = self.section.error or { } + self.section.error[section] = self.section.error[section] or { } + table.insert(self.section.error[section], msg or type) + + if type == "invalid" then + self.tag_invalid[section] = true + elseif type == "missing" then + self.tag_missing[section] = true + end + + self.tag_error[section] = true + self.map.save = false +end + +function AbstractValue.parse(self, section, novld) + local fvalue = self:formvalue(section) + local cvalue = self:cfgvalue(section) + + -- If favlue and cvalue are both tables and have the same content + -- make them identical + if type(fvalue) == "table" and type(cvalue) == "table" then + local equal = #fvalue == #cvalue + if equal then + for i=1, #fvalue do + if cvalue[i] ~= fvalue[i] then + equal = false + end + end + end + if equal then + fvalue = cvalue + end + end + + if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI + local val_err + fvalue, val_err = self:validate(fvalue, section) + fvalue = self:transform(fvalue) + + if not fvalue and not novld then + self:add_error(section, "invalid", val_err) + end + + if fvalue and (self.forcewrite or not (fvalue == cvalue)) then + if self:write(section, fvalue) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end + end + else -- Unset the UCI or error + if self.rmempty or self.optional then + if self:remove(section) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end + elseif cvalue ~= fvalue and not novld then + -- trigger validator with nil value to get custom user error msg. + local _, val_err = self:validate(nil, section) + self:add_error(section, "missing", val_err) + end + end +end + +-- Render if this value exists or if it is mandatory +function AbstractValue.render(self, s, scope) + if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then + scope = scope or {} + scope.section = s + scope.cbid = self:cbid(s) + Node.render(self, scope) + end +end + +-- Return the UCI value of this object +function AbstractValue.cfgvalue(self, section) + local value + if self.tag_error[section] then + value = self:formvalue(section) + else + value = self.map:get(section, self.option) + end + + if not value then + return nil + elseif not self.cast or self.cast == type(value) then + return value + elseif self.cast == "string" then + if type(value) == "table" then + return value[1] + end + elseif self.cast == "table" then + return { value } + end +end + +-- Validate the form value +function AbstractValue.validate(self, value) + if self.datatype and value then + if type(value) == "table" then + local v + for _, v in ipairs(value) do + if v and #v > 0 and not verify_datatype(self.datatype, v) then + return nil + end + end + else + if not verify_datatype(self.datatype, value) then + return nil + end + end + end + + return value +end + +AbstractValue.transform = AbstractValue.validate + + +-- Write to UCI +function AbstractValue.write(self, section, value) + return self.map:set(section, self.option, value) +end + +-- Remove from UCI +function AbstractValue.remove(self, section) + return self.map:del(section, self.option) +end + + + + +--[[ +Value - A one-line value + maxlength: The maximum length +]]-- +Value = class(AbstractValue) + +function Value.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/value" + self.keylist = {} + self.vallist = {} +end + +function Value.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function Value.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + + +-- DummyValue - This does nothing except being there +DummyValue = class(AbstractValue) + +function DummyValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/dvalue" + self.value = nil +end + +function DummyValue.cfgvalue(self, section) + local value + if self.value then + if type(self.value) == "function" then + value = self:value(section) + else + value = self.value + end + else + value = AbstractValue.cfgvalue(self, section) + end + return value +end + +function DummyValue.parse(self) + +end + + +--[[ +Flag - A flag being enabled or disabled +]]-- +Flag = class(AbstractValue) + +function Flag.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/fvalue" + + self.enabled = "1" + self.disabled = "0" + self.default = self.disabled +end + +-- A flag can only have two states: set or unset +function Flag.parse(self, section) + local fexists = self.map:formvalue( + FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option) + + if fexists then + local fvalue = self:formvalue(section) and self.enabled or self.disabled + if fvalue ~= self.default or (not self.optional and not self.rmempty) then + self:write(section, fvalue) + else + self:remove(section) + end + else + self:remove(section) + end +end + +function Flag.cfgvalue(self, section) + return AbstractValue.cfgvalue(self, section) or self.default +end + + +--[[ +ListValue - A one-line value predefined in a list + widget: The widget that will be used (select, radio) +]]-- +ListValue = class(AbstractValue) + +function ListValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/lvalue" + + self.keylist = {} + self.vallist = {} + self.size = 1 + self.widget = "select" +end + +function ListValue.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function ListValue.value(self, key, val, ...) + if luci.util.contains(self.keylist, key) then + return + end + + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) + + for i, deps in ipairs({...}) do + self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps} + end +end + +function ListValue.validate(self, val) + if luci.util.contains(self.keylist, val) then + return val + else + return nil + end +end + + + +--[[ +MultiValue - Multiple delimited values + widget: The widget that will be used (select, checkbox) + delimiter: The delimiter that will separate the values (default: " ") +]]-- +MultiValue = class(AbstractValue) + +function MultiValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/mvalue" + + self.keylist = {} + self.vallist = {} + + self.widget = "checkbox" + self.delimiter = " " +end + +function MultiValue.render(self, ...) + if self.widget == "select" and not self.size then + self.size = #self.vallist + end + + AbstractValue.render(self, ...) +end + +function MultiValue.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function MultiValue.value(self, key, val) + if luci.util.contains(self.keylist, key) then + return + end + + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + +function MultiValue.valuelist(self, section) + local val = self:cfgvalue(section) + + if not(type(val) == "string") then + return {} + end + + return luci.util.split(val, self.delimiter) +end + +function MultiValue.validate(self, val) + val = (type(val) == "table") and val or {val} + + local result + + for i, value in ipairs(val) do + if luci.util.contains(self.keylist, value) then + result = result and (result .. self.delimiter .. value) or value + end + end + + return result +end + + +StaticList = class(MultiValue) + +function StaticList.__init__(self, ...) + MultiValue.__init__(self, ...) + self.cast = "table" + self.valuelist = self.cfgvalue + + if not self.override_scheme + and self.map:get_scheme(self.section.sectiontype, self.option) then + local vs = self.map:get_scheme(self.section.sectiontype, self.option) + if self.value and vs.values and not self.override_values then + for k, v in pairs(vs.values) do + self:value(k, v) + end + end + end +end + +function StaticList.validate(self, value) + value = (type(value) == "table") and value or {value} + + local valid = {} + for i, v in ipairs(value) do + if luci.util.contains(self.keylist, v) then + table.insert(valid, v) + end + end + return valid +end + + +DynamicList = class(AbstractValue) + +function DynamicList.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/dynlist" + self.cast = "table" + self.keylist = {} + self.vallist = {} +end + +function DynamicList.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function DynamicList.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + +function DynamicList.write(self, section, value) + local t = { } + + if type(value) == "table" then + local x + for _, x in ipairs(value) do + if x and #x > 0 then + t[#t+1] = x + end + end + else + t = { value } + end + + if self.cast == "string" then + value = table.concat(t, " ") + else + value = t + end + + return AbstractValue.write(self, section, value) +end + +function DynamicList.cfgvalue(self, section) + local value = AbstractValue.cfgvalue(self, section) + + if type(value) == "string" then + local x + local t = { } + for x in value:gmatch("%S+") do + if #x > 0 then + t[#t+1] = x + end + end + value = t + end + + return value +end + +function DynamicList.formvalue(self, section) + local value = AbstractValue.formvalue(self, section) + + if type(value) == "string" then + if self.cast == "string" then + local x + local t = { } + for x in value:gmatch("%S+") do + t[#t+1] = x + end + value = t + else + value = { value } + end + end + + return value +end + + +--[[ +TextValue - A multi-line value + rows: Rows +]]-- +TextValue = class(AbstractValue) + +function TextValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/tvalue" +end + +--[[ +Button +]]-- +Button = class(AbstractValue) + +function Button.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/button" + self.inputstyle = nil + self.rmempty = true +end + + +FileUpload = class(AbstractValue) + +function FileUpload.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/upload" + if not self.map.upload_fields then + self.map.upload_fields = { self } + else + self.map.upload_fields[#self.map.upload_fields+1] = self + end +end + +function FileUpload.formcreated(self, section) + return AbstractValue.formcreated(self, section) or + self.map:formvalue("cbi.rlf."..section.."."..self.option) or + self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") +end + +function FileUpload.cfgvalue(self, section) + local val = AbstractValue.cfgvalue(self, section) + if val and fs.access(val) then + return val + end + return nil +end + +function FileUpload.formvalue(self, section) + local val = AbstractValue.formvalue(self, section) + if val then + if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and + not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") + then + return val + end + fs.unlink(val) + self.value = nil + end + return nil +end + +function FileUpload.remove(self, section) + local val = AbstractValue.formvalue(self, section) + if val and fs.access(val) then fs.unlink(val) end + return AbstractValue.remove(self, section) +end + + +FileBrowser = class(AbstractValue) + +function FileBrowser.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/browser" +end diff --git a/modules/base/luasrc/cbi/datatypes.lua b/modules/base/luasrc/cbi/datatypes.lua new file mode 100644 index 0000000000..c5f4ec0f0d --- /dev/null +++ b/modules/base/luasrc/cbi/datatypes.lua @@ -0,0 +1,345 @@ +--[[ + +LuCI - Configuration Bind Interface - Datatype Tests +(c) 2010 Jo-Philipp Wich + +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 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 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) + return hostname(val) or ipaddr(val) +end + +function network(val) + return uciname(val) or host(val) +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 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 diff --git a/modules/base/luasrc/config.lua b/modules/base/luasrc/config.lua new file mode 100644 index 0000000000..53db82b322 --- /dev/null +++ b/modules/base/luasrc/config.lua @@ -0,0 +1,42 @@ +--[[ +LuCI - Configuration + +Description: +Some LuCI configuration values read from uci file "luci" + + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local util = require "luci.util" +module("luci.config", + function(m) + if pcall(require, "luci.model.uci") then + local config = util.threadlocal() + setmetatable(m, { + __index = function(tbl, key) + if not config[key] then + config[key] = luci.model.uci.cursor():get_all("luci", key) + end + return config[key] + end + }) + end + end) diff --git a/modules/base/luasrc/dispatcher.lua b/modules/base/luasrc/dispatcher.lua new file mode 100644 index 0000000000..9e5b78d5e9 --- /dev/null +++ b/modules/base/luasrc/dispatcher.lua @@ -0,0 +1,959 @@ +--[[ +LuCI - Dispatcher + +Description: +The request dispatcher and module dispatcher generators + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +--- LuCI web dispatcher. +local fs = require "nixio.fs" +local sys = require "luci.sys" +local init = require "luci.init" +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 + + +--- Build the URL relative to the server webroot from given virtual path. +-- @param ... Virtual path +-- @return Relative URL +function build_url(...) + local path = {...} + local url = { http.getenv("SCRIPT_NAME") or "" } + + local k, v + for k, v in pairs(context.urltoken) do + url[#url+1] = "/;" + url[#url+1] = http.urlencode(k) + url[#url+1] = "=" + url[#url+1] = http.urlencode(v) + end + + 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 + + return table.concat(url, "") +end + +--- Check whether a dispatch node shall be visible +-- @param node Dispatch node +-- @return Boolean indicating whether the node should be visible +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 + +--- Return a sorted table of visible childs within a given node +-- @param node Dispatch node +-- @return Ordered table of child node names +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 + + +--- Send a 404 error code and render the "error404" template if available. +-- @param message Custom error message (optional) +-- @return false +function error404(message) + luci.http.status(404, "Not Found") + message = message or "Not Found" + + require("luci.template") + if not luci.util.copcall(luci.template.render, "error404") then + luci.http.prepare_content("text/plain") + luci.http.write(message) + end + return false +end + +--- Send a 500 error code and render the "error500" template if available. +-- @param message Custom error message (optional)# +-- @return false +function error500(message) + luci.util.perror(message) + if not context.template_header_sent then + luci.http.status(500, "Internal Server Error") + luci.http.prepare_content("text/plain") + luci.http.write(message) + else + require("luci.template") + if not luci.util.copcall(luci.template.render, "error500", {message=message}) then + luci.http.prepare_content("text/plain") + luci.http.write(message) + end + end + return false +end + +function authenticator.htmlauth(validator, accs, default) + local user = luci.http.formvalue("username") + local pass = luci.http.formvalue("password") + + if user and validator(user, pass) then + return user + end + + require("luci.i18n") + require("luci.template") + context.path = {} + luci.template.render("sysauth", {duser=default, fuser=user}) + return false + +end + +--- Dispatch an HTTP request. +-- @param request LuCI HTTP Request object +function httpdispatch(request, prefix) + luci.http.context.request = request + + local r = {} + context.request = r + context.urltoken = {} + + local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) + + if prefix then + for _, node in ipairs(prefix) do + r[#r+1] = node + end + end + + local tokensok = true + for node in pathinfo:gmatch("[^/]+") do + local tkey, tval + if tokensok then + tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)") + end + if tkey then + context.urltoken[tkey] = tval + else + tokensok = false + r[#r+1] = node + end + end + + local stat, err = util.coxpcall(function() + dispatch(context.request) + end, error500) + + luci.http.close() + + --context._disable_memtrace() +end + +--- Dispatches a LuCI virtual path. +-- @param request Virtual path +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 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 + require "luci.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 token = ctx.urltoken + 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 + return string.format( + ' %s="%s"', tostring(key), + luci.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 = luci.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; + }, {__index=function(table, key) + if key == "controller" then + return build_url() + elseif key == "REQUEST_URI" then + return build_url(unpack(ctx.requestpath)) + 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 " .. + "http://luci.subsignal.org/trac/newticket" + ) + + if track.sysauth then + local sauth = require "luci.sauth" + + 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 + local verifytoken = false + if not sess then + sess = luci.http.getcookie("sysauth") + sess = sess and sess:match("^[a-f0-9]*$") + verifytoken = true + end + + local sdat = sauth.read(sess) + local user + + if sdat then + if not verifytoken or ctx.urltoken.stok == sdat.token then + user = sdat.user + end + else + local eu = http.getenv("HTTP_AUTH_USER") + local ep = http.getenv("HTTP_AUTH_PASS") + if eu and ep and luci.sys.user.checkpasswd(eu, ep) then + authen = function() return eu end + end + end + + if not util.contains(accs, user) then + if authen then + ctx.urltoken.stok = nil + local user, sess = authen(luci.sys.user.checkpasswd, accs, def) + if not user or not util.contains(accs, user) then + return + else + local sid = sess or luci.sys.uniqueid(16) + if not sess then + local token = luci.sys.uniqueid(16) + sauth.reap() + sauth.write(sid, { + user=user, + token=token, + secret=luci.sys.uniqueid(16) + }) + ctx.urltoken.stok = token + end + luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path="..build_url()) + ctx.authsession = sid + ctx.authuser = user + end + else + luci.http.status(403, "Forbidden") + return + end + else + ctx.authsession = sess + ctx.authuser = user + end + end + + if track.setgroup then + luci.sys.process.setgroup(track.setgroup) + end + + if track.setuser then + luci.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 + +--- Generate the dispatching index using the best possible strategy. +function createindex() + local path = luci.util.libpath() .. "/controller/" + local suff = { ".lua", ".lua.gz" } + + if luci.util.copcall(require, "luci.fastindex") then + createindex_fastindex(path, suff) + else + createindex_plain(path, suff) + end +end + +--- Generate the dispatching index using the fastindex C-indexer. +-- @param path Controller base directory +-- @param suffixes Controller file suffixes +function createindex_fastindex(path, suffixes) + index = {} + + if not fi then + fi = luci.fastindex.new("index") + for _, suffix in ipairs(suffixes) do + fi.add(path .. "*" .. suffix) + fi.add(path .. "*/*" .. suffix) + end + end + fi.scan() + + for k, v in pairs(fi.indexes) do + index[v[2]] = v[1] + end +end + +--- Generate the dispatching index using the native file-cache based strategy. +-- @param path Controller base directory +-- @param suffixes Controller file suffixes +function createindex_plain(path, suffixes) + local controllers = { } + for _, suffix in ipairs(suffixes) do + nixio.util.consume((fs.glob(path .. "*" .. suffix)), controllers) + nixio.util.consume((fs.glob(path .. "*/*" .. suffix)), controllers) + 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 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 i,c in ipairs(controllers) do + local modname = "luci.controller." .. c:sub(#path+1, #c):gsub("/", ".") + for _, suffix in ipairs(suffixes) do + modname = modname:gsub(suffix.."$", "") + end + + local mod = require(modname) + assert(mod ~= true, + "Invalid controller file found\n" .. + "The file '" .. c .. "' 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 '" .. c .. "' 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 + +--- Create the dispatching tree from the index. +-- 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 + +--- Register a tree modifier. +-- @param func Modifier function +-- @param order Modifier order value (optional) +function modifier(func, order) + context.modifiers[#context.modifiers+1] = { + func = func, + order = order or 0, + module + = getfenv(2)._NAME + } +end + +--- Clone a node of the dispatching tree to another position. +-- @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 +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 + +--- Create a new dispatching node and define common parameters. +-- @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 +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 + +--- Fetch or create a dispatching node without setting the target module or +-- enabling the node. +-- @param ... Virtual path +-- @return Dispatching tree node +function get(...) + return _create_node({...}) +end + +--- Fetch or create a new dispatching node. +-- @param ... Virtual path +-- @return Dispatching tree node +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 + +--- Alias the first (lowest order) page automatically +function firstchild() + return { type = "firstchild", target = _firstchild } +end + +--- Create a redirect to another dispatching node. +-- @param ... Virtual path destination +function alias(...) + local req = {...} + return function(...) + for _, r in ipairs({...}) do + req[#req+1] = r + end + + dispatch(req) + end +end + +--- Rewrite the first x path values of the request. +-- @param n Number of path values to replace +-- @param ... Virtual path to replace removed path values with +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 + +--- Create a function-call dispatching target. +-- @param name Target function of local controller +-- @param ... Additional parameters passed to the function +function call(name, ...) + return {type = "call", argv = {...}, name = name, target = _call} +end + + +local _template = function(self, ...) + require "luci.template".render(self.view) +end + +--- Create a template render dispatching target. +-- @param name Template to be rendered +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 + +--- Create a CBI model dispatching target. +-- @param model CBI model to be rendered +function cbi(model, config) + return {type = "cbi", 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 + +--- Create a combined dispatching target for non argv and argv requests. +-- @param trg1 Overview Target +-- @param trg2 Detail Target +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 + +--- Create a CBI form model dispatching target. +-- @param model CBI form model tpo be rendered +function form(model) + return {type = "cbi", model = model, target = _form} +end + +--- Access the luci.i18n translate() api. +-- @class function +-- @name translate +-- @param text Text to translate +translate = i18n.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. +function _(text) + return text +end diff --git a/modules/base/luasrc/http.lua b/modules/base/luasrc/http.lua new file mode 100644 index 0000000000..c53307a5a1 --- /dev/null +++ b/modules/base/luasrc/http.lua @@ -0,0 +1,344 @@ +--[[ +LuCI - HTTP-Interaction + +Description: +HTTP-Header manipulator and form variable preprocessor + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +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 + +--- LuCI Web Framework high-level HTTP functions. +module "luci.http" + +context = util.threadlocal() + +Request = util.class() +function Request.__init__(self, env, sourcein, sinkerr) + self.input = sourcein + self.error = sinkerr + + + -- File handler + self.filehandler = function() end + + -- 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 +end + +function Request._parse_input(self) + protocol.parse_message_body( + self.input, + self.message, + self.filehandler + ) + self.parsed_input = true +end + +--- Close the HTTP-Connection. +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 + +--- Return the request content if the request was of unknown type. +-- @return HTTP request body +-- @return HTTP request body length +function content() + return context.request:content() +end + +--- Get a certain HTTP input value or a table of all input values. +-- @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 +function formvalue(name, noparse) + return context.request:formvalue(name, noparse) +end + +--- Get a table of all HTTP input values with a certain prefix. +-- @param prefix Prefix +-- @return Table of all HTTP input values with given prefix +function formvaluetable(prefix) + return context.request:formvaluetable(prefix) +end + +--- Get the value of a certain HTTP-Cookie. +-- @param name Cookie Name +-- @return String containing cookie data +function getcookie(name) + return context.request:getcookie(name) +end + +--- Get the value of a certain HTTP environment variable +-- or the environment table itself. +-- @param name Environment variable +-- @return HTTP environment value or environment table +function getenv(name) + return context.request:getenv(name) +end + +--- Set a handler function for incoming user file uploads. +-- @param callback Handler function +function setfilehandler(callback) + return context.request:setfilehandler(callback) +end + +--- Send a HTTP-Header. +-- @param key Header key +-- @param value Header value +function header(key, value) + if not context.headers then + context.headers = {} + end + context.headers[key:lower()] = value + coroutine.yield(2, key, value) +end + +--- Set the mime type of following content data. +-- @param mime Mimetype of following content +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 + +--- Get the RAW HTTP input source +-- @return HTTP LTN12 source +function source() + return context.request.input +end + +--- Set the HTTP status code and status message. +-- @param code Status code +-- @param message Status message +function status(code, message) + code = code or 200 + message = message or "OK" + context.status = code + coroutine.yield(1, code, message) +end + +--- 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. +-- @param content Content chunk +-- @param src_err Error object from source (optional) +-- @see 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 + +--- Splice data from a filedescriptor to the client. +-- @param fp File descriptor +-- @param size Bytes to splice (optional) +function splice(fd, size) + coroutine.yield(6, fd, size) +end + +--- Redirects the client to a new URL and closes the connection. +-- @param url Target URL +function redirect(url) + status(302, "Found") + header("Location", url) + close() +end + +--- Create a querystring out of a table of key - value pairs. +-- @param table Query string source table +-- @return Encoded HTTP query string +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 + +--- 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 +urldecode = protocol.urldecode + +--- Return the URL-encoded equivalent of a string. +-- @param str Source string +-- @return URL-encoded string +-- @see urldecode +urlencode = protocol.urlencode + +--- Send the given data as JSON encoded string. +-- @param data Data to send +function write_json(x) + if x == nil then + write("null") + elseif type(x) == "table" then + local k, v + if type(next(x)) == "number" then + write("[ ") + for k, v in ipairs(x) do + write_json(v) + if next(x, k) then + write(", ") + end + end + write(" ]") + else + write("{ ") + for k, v in pairs(x) do + write("%q: " % k) + write_json(v) + if next(x, k) then + write(", ") + end + end + write(" }") + end + elseif type(x) == "number" or type(x) == "boolean" then + if (x ~= x) then + -- NaN is the only value that doesn't equal to itself. + write("Number.NaN") + else + write(tostring(x)) + end + else + write('"%s"' % tostring(x):gsub('["%z\1-\31]', function(c) + return '\\u%04x' % c:byte(1) + end)) + end +end diff --git a/modules/base/luasrc/http/protocol.lua b/modules/base/luasrc/http/protocol.lua new file mode 100644 index 0000000000..0d41550b23 --- /dev/null +++ b/modules/base/luasrc/http/protocol.lua @@ -0,0 +1,688 @@ +--[[ + +HTTP protocol implementation for LuCI +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +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$ + +]]-- + +--- 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", package.seeall) + +local ltn12 = require("luci.ltn12") + +HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size + +--- Decode an urlencoded string - optionally without decoding +-- the "+" sign to " " - and return the decoded string. +-- @param str Input string in x-www-urlencoded format +-- @param no_plus Don't decode "+" signs to spaces +-- @return The decoded string +-- @see urlencode +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 + +--- 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. +-- @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 +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 + +--- Encode given string to x-www-urlencoded format. +-- @param str String to encode +-- @return String containing the encoded data +-- @see urldecode +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 + +--- 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. +-- @param tbl Table with the values +-- @return String containing encoded values +-- @see urldecode_params +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. +-- @param tbl Table where parameter should be created +-- @param key Parameter name +-- @return Always nil +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) +-- 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. +-- @param tbl Table containing the previously initialized parameter value +-- @param key Parameter name +-- @param chunk String containing the data to append +-- @return Always nil +-- @see __initval +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. +-- @param tbl Table containing the previously initialized parameter value +-- @param key Parameter name +-- @param handler Function which transforms the parameter value +-- @return Always nil +-- @see __initval +-- @see __appendval +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 + + +--- 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. +-- @param sock Readable network socket +-- @return Ltn12 source function +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 + +--- 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) +-- @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 +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 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 + +--- 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. +-- @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 +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 + +--- Try to extract an http message header including information like protocol +-- version, message headers and resulting CGI environment variables from the +-- given ltn12 source. +-- @param src Ltn12 source function +-- @return HTTP message object +-- @see parse_message_body +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 + +--- 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. +-- @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 +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 + sink = filecb + + -- ... else append to .content + else + msg.content = "" + msg.content_length = 0 + + sink = function( chunk, err ) + 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 err then + return true + end + end + + return true + end +end + +--- Table containing human readable messages for several http status codes. +-- @class table +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", +} diff --git a/modules/base/luasrc/http/protocol/conditionals.lua b/modules/base/luasrc/http/protocol/conditionals.lua new file mode 100644 index 0000000000..75e1f7b37c --- /dev/null +++ b/modules/base/luasrc/http/protocol/conditionals.lua @@ -0,0 +1,153 @@ +--[[ + +HTTP protocol implementation for LuCI - RFC2616 / 14.19, 14.24 - 14.28 +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +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$ + +]]-- + +--- 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", package.seeall) + +local date = require("luci.http.protocol.date") + + +--- Implement 14.19 / ETag. +-- @param stat A file.stat structure +-- @return String containing the generated tag suitable for ETag headers +function mk_etag( stat ) + if stat ~= nil then + return string.format( '"%x-%x-%x"', stat.ino, stat.size, stat.mtime ) + end +end + +--- 14.24 / If-Match +-- Test whether the given message object contains an "If-Match" header and +-- compare it against the given stat object. +-- @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 +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 + +--- 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. +-- @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 +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 + +--- 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. +-- @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 +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 + +--- 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. +-- @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 +function if_range( req, stat ) + -- Sorry, no subranges (yet) + return false, 412 +end + +--- 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. +-- @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 +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 diff --git a/modules/base/luasrc/http/protocol/date.lua b/modules/base/luasrc/http/protocol/date.lua new file mode 100644 index 0000000000..83d11e2c25 --- /dev/null +++ b/modules/base/luasrc/http/protocol/date.lua @@ -0,0 +1,115 @@ +--[[ + +HTTP protocol implementation for LuCI - date handling +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +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$ + +]]-- + +--- LuCI http protocol implementation - date helper class. +-- 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" +} + +--- Return the time offset in seconds between the UTC and given time zone. +-- @param tz Symbolic or numeric timezone specifier +-- @return Time offset to UTC in seconds +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 + +--- Parse given HTTP date string and convert it to unix epoch time. +-- @param data String containing the date +-- @return Unix epoch time +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 + +--- Convert the given unix epoch time to valid HTTP date string. +-- @param time Unix epoch time +-- @return String containing the formatted date +function to_http(time) + return os.date( "%a, %d %b %Y %H:%M:%S GMT", time ) +end + +--- Compare two dates which can either be unix epoch times or HTTP date strings. +-- @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 +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 diff --git a/modules/base/luasrc/http/protocol/mime.lua b/modules/base/luasrc/http/protocol/mime.lua new file mode 100644 index 0000000000..c878160664 --- /dev/null +++ b/modules/base/luasrc/http/protocol/mime.lua @@ -0,0 +1,99 @@ +--[[ + +HTTP protocol implementation for LuCI - mime handling +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +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$ + +]]-- + +--- 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", package.seeall) + +require("luci.util") + +--- MIME mapping table containg extension - mimetype relations. +-- @class table +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"; +} + +--- Extract extension from a filename and return corresponding mime-type or +-- "application/octet-stream" if the extension is unknown. +-- @param filename The filename for which the mime type is guessed +-- @return String containign the determined mime type +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 + +--- Return corresponding extension for a given mime type or nil if the +-- given mime-type is unknown. +-- @param mimetype The mimetype to retrieve the extension from +-- @return String with the extension or nil for unknown type +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 diff --git a/modules/base/luasrc/i18n.lua b/modules/base/luasrc/i18n.lua new file mode 100644 index 0000000000..545a8aed93 --- /dev/null +++ b/modules/base/luasrc/i18n.lua @@ -0,0 +1,104 @@ +--[[ +LuCI - Internationalisation + +Description: +A very minimalistic but yet effective internationalisation module + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +--- LuCI translation library. +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" + +--- Clear the translation table. +function clear() +end + +--- Load a translation and copy its data into the translation table. +-- @param file Language file +-- @param lang Two-letter language code +-- @param force Force reload even if already loaded (optional) +-- @return Success status +function load(file, lang, force) +end + +--- Load a translation file using the default translation language. +-- Alternatively load the translation of the fallback language. +-- @param file Language file +-- @param force Force reload even if already loaded (optional) +function loadc(file, force) +end + +--- Set the context default translation language. +-- @param lang Two-letter language code +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 + +--- Return the translated value for a specific translation key. +-- @param key Default translation text +-- @return Translated string +function translate(key) + return tparser.translate(key) or key +end + +--- Return the translated value for a specific translation key and use it as sprintf pattern. +-- @param key Default translation text +-- @param ... Format parameters +-- @return Translated and formatted string +function translatef(key, ...) + return tostring(translate(key)):format(...) +end + +--- 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 tostring(translate(...)) +-- @param key Default translation text +-- @return Translated string +function string(key) + return tostring(translate(key)) +end + +--- 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 tostring(translatef(...)) +-- @param key Default translation text +-- @param ... Format parameters +-- @return Translated and formatted string +function stringf(key, ...) + return tostring(translate(key)):format(...) +end diff --git a/modules/base/luasrc/luasrc/cacheloader.lua b/modules/base/luasrc/luasrc/cacheloader.lua deleted file mode 100644 index 942c4b7b48..0000000000 --- a/modules/base/luasrc/luasrc/cacheloader.lua +++ /dev/null @@ -1,23 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface - -Copyright 2008 Steven Barth -Copyright 2008 Jo-Philipp Wich - -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 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 \ No newline at end of file diff --git a/modules/base/luasrc/luasrc/cbi.lua b/modules/base/luasrc/luasrc/cbi.lua deleted file mode 100644 index ae570b1556..0000000000 --- a/modules/base/luasrc/luasrc/cbi.lua +++ /dev/null @@ -1,1850 +0,0 @@ ---[[ -LuCI - Configuration Bind Interface - -Description: -Offers an interface for binding configuration values to certain -data types. Supports value and range validation and basic dependencies. - -FileId: -$Id$ - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- -module("luci.cbi", package.seeall) - -require("luci.template") -local util = require("luci.util") -require("luci.http") - - ---local event = require "luci.sys.event" -local fs = require("nixio.fs") -local uci = require("luci.model.uci") -local datatypes = require("luci.cbi.datatypes") -local class = util.class -local instanceof = util.instanceof - -FORM_NODATA = 0 -FORM_PROCEED = 0 -FORM_VALID = 1 -FORM_DONE = 1 -FORM_INVALID = -1 -FORM_CHANGED = 2 -FORM_SKIP = 4 - -AUTO = true - -CREATE_PREFIX = "cbi.cts." -REMOVE_PREFIX = "cbi.rts." -RESORT_PREFIX = "cbi.sts." -FEXIST_PREFIX = "cbi.cbe." - --- Loads a CBI map from given file, creating an environment and returns it -function load(cbimap, ...) - local fs = require "nixio.fs" - local i18n = require "luci.i18n" - require("luci.config") - require("luci.util") - - local upldir = "/lib/uci/upload/" - local cbidir = luci.util.libpath() .. "/model/cbi/" - local func, err - - if fs.access(cbidir..cbimap..".lua") then - func, err = loadfile(cbidir..cbimap..".lua") - elseif fs.access(cbimap) then - func, err = loadfile(cbimap) - else - func, err = nil, "Model '" .. cbimap .. "' not found!" - end - - assert(func, err) - - local env = { - translate=i18n.translate, - translatef=i18n.translatef, - arg={...} - } - - setfenv(func, setmetatable(env, {__index = - function(tbl, key) - return rawget(tbl, key) or _M[key] or _G[key] - end})) - - local maps = { func() } - local uploads = { } - local has_upload = false - - for i, map in ipairs(maps) do - if not instanceof(map, Node) then - error("CBI map returns no valid map object!") - return nil - else - map:prepare() - if map.upload_fields then - has_upload = true - for _, field in ipairs(map.upload_fields) do - uploads[ - field.config .. '.' .. - (field.section.sectiontype or '1') .. '.' .. - field.option - ] = true - end - end - end - end - - if has_upload then - local uci = luci.model.uci.cursor() - local prm = luci.http.context.request.message.params - local fd, cbid - - luci.http.setfilehandler( - function( field, chunk, eof ) - if not field then return end - if field.name and not cbid then - local c, s, o = field.name:gmatch( - "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)" - )() - - if c and s and o then - local t = uci:get( c, s ) or s - if uploads[c.."."..t.."."..o] then - local path = upldir .. field.name - fd = io.open(path, "w") - if fd then - cbid = field.name - prm[cbid] = path - end - end - end - end - - if field.name == cbid and fd then - fd:write(chunk) - end - - if eof and fd then - fd:close() - fd = nil - cbid = nil - end - end - ) - end - - return maps -end - --- --- Compile a datatype specification into a parse tree for evaluation later on --- -local cdt_cache = { } - -function compile_datatype(code) - local i - local pos = 0 - local esc = false - local depth = 0 - local stack = { } - - for i = 1, #code+1 do - local byte = code:byte(i) or 44 - if esc then - esc = false - elseif byte == 92 then - esc = true - elseif byte == 40 or byte == 44 then - if depth <= 0 then - if pos < i then - local label = code:sub(pos, i-1) - :gsub("\\(.)", "%1") - :gsub("^%s+", "") - :gsub("%s+$", "") - - if #label > 0 and tonumber(label) then - stack[#stack+1] = tonumber(label) - elseif label:match("^'.*'$") or label:match('^".*"$') then - stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1") - elseif type(datatypes[label]) == "function" then - stack[#stack+1] = datatypes[label] - stack[#stack+1] = { } - else - error("Datatype error, bad token %q" % label) - end - end - pos = i + 1 - end - depth = depth + (byte == 40 and 1 or 0) - elseif byte == 41 then - depth = depth - 1 - if depth <= 0 then - if type(stack[#stack-1]) ~= "function" then - error("Datatype error, argument list follows non-function") - end - stack[#stack] = compile_datatype(code:sub(pos, i-1)) - pos = i + 1 - end - end - end - - return stack -end - -function verify_datatype(dt, value) - if dt and #dt > 0 then - if not cdt_cache[dt] then - local c = compile_datatype(dt) - if c and type(c[1]) == "function" then - cdt_cache[dt] = c - else - error("Datatype error, not a function expression") - end - end - if cdt_cache[dt] then - return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2])) - end - end - return true -end - - --- Node pseudo abstract class -Node = class() - -function Node.__init__(self, title, description) - self.children = {} - self.title = title or "" - self.description = description or "" - self.template = "cbi/node" -end - --- hook helper -function Node._run_hook(self, hook) - if type(self[hook]) == "function" then - return self[hook](self) - end -end - -function Node._run_hooks(self, ...) - local f - local r = false - for _, f in ipairs(arg) do - if type(self[f]) == "function" then - self[f](self) - r = true - end - end - return r -end - --- Prepare nodes -function Node.prepare(self, ...) - for k, child in ipairs(self.children) do - child:prepare(...) - end -end - --- Append child nodes -function Node.append(self, obj) - table.insert(self.children, obj) -end - --- Parse this node and its children -function Node.parse(self, ...) - for k, child in ipairs(self.children) do - child:parse(...) - end -end - --- Render this node -function Node.render(self, scope) - scope = scope or {} - scope.self = self - - luci.template.render(self.template, scope) -end - --- Render the children -function Node.render_children(self, ...) - local k, node - for k, node in ipairs(self.children) do - node.last_child = (k == #self.children) - node:render(...) - end -end - - ---[[ -A simple template element -]]-- -Template = class(Node) - -function Template.__init__(self, template) - Node.__init__(self) - self.template = template -end - -function Template.render(self) - luci.template.render(self.template, {self=self}) -end - -function Template.parse(self, readinput) - self.readinput = (readinput ~= false) - return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA -end - - ---[[ -Map - A map describing a configuration file -]]-- -Map = class(Node) - -function Map.__init__(self, config, ...) - Node.__init__(self, ...) - - self.config = config - self.parsechain = {self.config} - self.template = "cbi/map" - self.apply_on_parse = nil - self.readinput = true - self.proceed = false - self.flow = {} - - self.uci = uci.cursor() - self.save = true - - self.changed = false - - if not self.uci:load(self.config) then - error("Unable to read UCI data: " .. self.config) - end -end - -function Map.formvalue(self, key) - return self.readinput and luci.http.formvalue(key) -end - -function Map.formvaluetable(self, key) - return self.readinput and luci.http.formvaluetable(key) or {} -end - -function Map.get_scheme(self, sectiontype, option) - if not option then - return self.scheme and self.scheme.sections[sectiontype] - else - return self.scheme and self.scheme.variables[sectiontype] - and self.scheme.variables[sectiontype][option] - end -end - -function Map.submitstate(self) - return self:formvalue("cbi.submit") -end - --- Chain foreign config -function Map.chain(self, config) - table.insert(self.parsechain, config) -end - -function Map.state_handler(self, state) - return state -end - --- Use optimized UCI writing -function Map.parse(self, readinput, ...) - self.readinput = (readinput ~= false) - self:_run_hooks("on_parse") - - if self:formvalue("cbi.skip") then - self.state = FORM_SKIP - return self:state_handler(self.state) - end - - Node.parse(self, ...) - - if self.save then - self:_run_hooks("on_save", "on_before_save") - for i, config in ipairs(self.parsechain) do - self.uci:save(config) - end - self:_run_hooks("on_after_save") - if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then - self:_run_hooks("on_before_commit") - for i, config in ipairs(self.parsechain) do - self.uci:commit(config) - - -- Refresh data because commit changes section names - self.uci:load(config) - end - self:_run_hooks("on_commit", "on_after_commit", "on_before_apply") - if self.apply_on_parse then - self.uci:apply(self.parsechain) - self:_run_hooks("on_apply", "on_after_apply") - else - -- This is evaluated by the dispatcher and delegated to the - -- template which in turn fires XHR to perform the actual - -- apply actions. - self.apply_needed = true - end - - -- Reparse sections - Node.parse(self, true) - - end - for i, config in ipairs(self.parsechain) do - self.uci:unload(config) - end - if type(self.commit_handler) == "function" then - self:commit_handler(self:submitstate()) - end - end - - if self:submitstate() then - if not self.save then - self.state = FORM_INVALID - elseif self.proceed then - self.state = FORM_PROCEED - else - self.state = self.changed and FORM_CHANGED or FORM_VALID - end - else - self.state = FORM_NODATA - end - - return self:state_handler(self.state) -end - -function Map.render(self, ...) - self:_run_hooks("on_init") - Node.render(self, ...) -end - --- Creates a child section -function Map.section(self, class, ...) - if instanceof(class, AbstractSection) then - local obj = class(self, ...) - self:append(obj) - return obj - else - error("class must be a descendent of AbstractSection") - end -end - --- UCI add -function Map.add(self, sectiontype) - return self.uci:add(self.config, sectiontype) -end - --- UCI set -function Map.set(self, section, option, value) - if type(value) ~= "table" or #value > 0 then - if option then - return self.uci:set(self.config, section, option, value) - else - return self.uci:set(self.config, section, value) - end - else - return Map.del(self, section, option) - end -end - --- UCI del -function Map.del(self, section, option) - if option then - return self.uci:delete(self.config, section, option) - else - return self.uci:delete(self.config, section) - end -end - --- UCI get -function Map.get(self, section, option) - if not section then - return self.uci:get_all(self.config) - elseif option then - return self.uci:get(self.config, section, option) - else - return self.uci:get_all(self.config, section) - end -end - ---[[ -Compound - Container -]]-- -Compound = class(Node) - -function Compound.__init__(self, ...) - Node.__init__(self) - self.template = "cbi/compound" - self.children = {...} -end - -function Compound.populate_delegator(self, delegator) - for _, v in ipairs(self.children) do - v.delegator = delegator - end -end - -function Compound.parse(self, ...) - local cstate, state = 0 - - for k, child in ipairs(self.children) do - cstate = child:parse(...) - state = (not state or cstate < state) and cstate or state - end - - return state -end - - ---[[ -Delegator - Node controller -]]-- -Delegator = class(Node) -function Delegator.__init__(self, ...) - Node.__init__(self, ...) - self.nodes = {} - self.defaultpath = {} - self.pageaction = false - self.readinput = true - self.allow_reset = false - self.allow_cancel = false - self.allow_back = false - self.allow_finish = false - self.template = "cbi/delegator" -end - -function Delegator.set(self, name, node) - assert(not self.nodes[name], "Duplicate entry") - - self.nodes[name] = node -end - -function Delegator.add(self, name, node) - node = self:set(name, node) - self.defaultpath[#self.defaultpath+1] = name -end - -function Delegator.insert_after(self, name, after) - local n = #self.chain + 1 - for k, v in ipairs(self.chain) do - if v == after then - n = k + 1 - break - end - end - table.insert(self.chain, n, name) -end - -function Delegator.set_route(self, ...) - local n, chain, route = 0, self.chain, {...} - for i = 1, #chain do - if chain[i] == self.current then - n = i - break - end - end - for i = 1, #route do - n = n + 1 - chain[n] = route[i] - end - for i = n + 1, #chain do - chain[i] = nil - end -end - -function Delegator.get(self, name) - local node = self.nodes[name] - - if type(node) == "string" then - node = load(node, name) - end - - if type(node) == "table" and getmetatable(node) == nil then - node = Compound(unpack(node)) - end - - return node -end - -function Delegator.parse(self, ...) - if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then - if self:_run_hooks("on_cancel") then - return FORM_DONE - end - end - - if not Map.formvalue(self, "cbi.delg.current") then - self:_run_hooks("on_init") - end - - local newcurrent - self.chain = self.chain or self:get_chain() - self.current = self.current or self:get_active() - self.active = self.active or self:get(self.current) - assert(self.active, "Invalid state") - - local stat = FORM_DONE - if type(self.active) ~= "function" then - self.active:populate_delegator(self) - stat = self.active:parse() - else - self:active() - end - - if stat > FORM_PROCEED then - if Map.formvalue(self, "cbi.delg.back") then - newcurrent = self:get_prev(self.current) - else - newcurrent = self:get_next(self.current) - end - elseif stat < FORM_PROCEED then - return stat - end - - - if not Map.formvalue(self, "cbi.submit") then - return FORM_NODATA - elseif stat > FORM_PROCEED - and (not newcurrent or not self:get(newcurrent)) then - return self:_run_hook("on_done") or FORM_DONE - else - self.current = newcurrent or self.current - self.active = self:get(self.current) - if type(self.active) ~= "function" then - self.active:populate_delegator(self) - local stat = self.active:parse(false) - if stat == FORM_SKIP then - return self:parse(...) - else - return FORM_PROCEED - end - else - return self:parse(...) - end - end -end - -function Delegator.get_next(self, state) - for k, v in ipairs(self.chain) do - if v == state then - return self.chain[k+1] - end - end -end - -function Delegator.get_prev(self, state) - for k, v in ipairs(self.chain) do - if v == state then - return self.chain[k-1] - end - end -end - -function Delegator.get_chain(self) - local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath - return type(x) == "table" and x or {x} -end - -function Delegator.get_active(self) - return Map.formvalue(self, "cbi.delg.current") or self.chain[1] -end - ---[[ -Page - A simple node -]]-- - -Page = class(Node) -Page.__init__ = Node.__init__ -Page.parse = function() end - - ---[[ -SimpleForm - A Simple non-UCI form -]]-- -SimpleForm = class(Node) - -function SimpleForm.__init__(self, config, title, description, data) - Node.__init__(self, title, description) - self.config = config - self.data = data or {} - self.template = "cbi/simpleform" - self.dorender = true - self.pageaction = false - self.readinput = true -end - -SimpleForm.formvalue = Map.formvalue -SimpleForm.formvaluetable = Map.formvaluetable - -function SimpleForm.parse(self, readinput, ...) - self.readinput = (readinput ~= false) - - if self:formvalue("cbi.skip") then - return FORM_SKIP - end - - if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then - return FORM_DONE - end - - if self:submitstate() then - Node.parse(self, 1, ...) - end - - local valid = true - for k, j in ipairs(self.children) do - for i, v in ipairs(j.children) do - valid = valid - and (not v.tag_missing or not v.tag_missing[1]) - and (not v.tag_invalid or not v.tag_invalid[1]) - and (not v.error) - end - end - - local state = - not self:submitstate() and FORM_NODATA - or valid and FORM_VALID - or FORM_INVALID - - self.dorender = not self.handle - if self.handle then - local nrender, nstate = self:handle(state, self.data) - self.dorender = self.dorender or (nrender ~= false) - state = nstate or state - end - return state -end - -function SimpleForm.render(self, ...) - if self.dorender then - Node.render(self, ...) - end -end - -function SimpleForm.submitstate(self) - return self:formvalue("cbi.submit") -end - -function SimpleForm.section(self, class, ...) - if instanceof(class, AbstractSection) then - local obj = class(self, ...) - self:append(obj) - return obj - else - error("class must be a descendent of AbstractSection") - end -end - --- Creates a child field -function SimpleForm.field(self, class, ...) - local section - for k, v in ipairs(self.children) do - if instanceof(v, SimpleSection) then - section = v - break - end - end - if not section then - section = self:section(SimpleSection) - end - - if instanceof(class, AbstractValue) then - local obj = class(self, section, ...) - obj.track_missing = true - section:append(obj) - return obj - else - error("class must be a descendent of AbstractValue") - end -end - -function SimpleForm.set(self, section, option, value) - self.data[option] = value -end - - -function SimpleForm.del(self, section, option) - self.data[option] = nil -end - - -function SimpleForm.get(self, section, option) - return self.data[option] -end - - -function SimpleForm.get_scheme() - return nil -end - - -Form = class(SimpleForm) - -function Form.__init__(self, ...) - SimpleForm.__init__(self, ...) - self.embedded = true -end - - ---[[ -AbstractSection -]]-- -AbstractSection = class(Node) - -function AbstractSection.__init__(self, map, sectiontype, ...) - Node.__init__(self, ...) - self.sectiontype = sectiontype - self.map = map - self.config = map.config - self.optionals = {} - self.defaults = {} - self.fields = {} - self.tag_error = {} - self.tag_invalid = {} - self.tag_deperror = {} - self.changed = false - - self.optional = true - self.addremove = false - self.dynamic = false -end - --- Define a tab for the section -function AbstractSection.tab(self, tab, title, desc) - self.tabs = self.tabs or { } - self.tab_names = self.tab_names or { } - - self.tab_names[#self.tab_names+1] = tab - self.tabs[tab] = { - title = title, - description = desc, - childs = { } - } -end - --- Check whether the section has tabs -function AbstractSection.has_tabs(self) - return (self.tabs ~= nil) and (next(self.tabs) ~= nil) -end - --- Appends a new option -function AbstractSection.option(self, class, option, ...) - if instanceof(class, AbstractValue) then - local obj = class(self.map, self, option, ...) - self:append(obj) - self.fields[option] = obj - return obj - elseif class == true then - error("No valid class was given and autodetection failed.") - else - error("class must be a descendant of AbstractValue") - end -end - --- Appends a new tabbed option -function AbstractSection.taboption(self, tab, ...) - - assert(tab and self.tabs and self.tabs[tab], - "Cannot assign option to not existing tab %q" % tostring(tab)) - - local l = self.tabs[tab].childs - local o = AbstractSection.option(self, ...) - - if o then l[#l+1] = o end - - return o -end - --- Render a single tab -function AbstractSection.render_tab(self, tab, ...) - - assert(tab and self.tabs and self.tabs[tab], - "Cannot render not existing tab %q" % tostring(tab)) - - local k, node - for k, node in ipairs(self.tabs[tab].childs) do - node.last_child = (k == #self.tabs[tab].childs) - node:render(...) - end -end - --- Parse optional options -function AbstractSection.parse_optionals(self, section) - if not self.optional then - return - end - - self.optionals[section] = {} - - local field = self.map:formvalue("cbi.opt."..self.config.."."..section) - for k,v in ipairs(self.children) do - if v.optional and not v:cfgvalue(section) and not self:has_tabs() then - if field == v.option then - field = nil - self.map.proceed = true - else - table.insert(self.optionals[section], v) - end - end - end - - if field and #field > 0 and self.dynamic then - self:add_dynamic(field) - end -end - --- Add a dynamic option -function AbstractSection.add_dynamic(self, field, optional) - local o = self:option(Value, field, field) - o.optional = optional -end - --- Parse all dynamic options -function AbstractSection.parse_dynamic(self, section) - if not self.dynamic then - return - end - - local arr = luci.util.clone(self:cfgvalue(section)) - local form = self.map:formvaluetable("cbid."..self.config.."."..section) - for k, v in pairs(form) do - arr[k] = v - end - - for key,val in pairs(arr) do - local create = true - - for i,c in ipairs(self.children) do - if c.option == key then - create = false - end - end - - if create and key:sub(1, 1) ~= "." then - self.map.proceed = true - self:add_dynamic(key, true) - end - end -end - --- Returns the section's UCI table -function AbstractSection.cfgvalue(self, section) - return self.map:get(section) -end - --- Push events -function AbstractSection.push_events(self) - --luci.util.append(self.map.events, self.events) - self.map.changed = true -end - --- Removes the section -function AbstractSection.remove(self, section) - self.map.proceed = true - return self.map:del(section) -end - --- Creates the section -function AbstractSection.create(self, section) - local stat - - if section then - stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype) - else - section = self.map:add(self.sectiontype) - stat = section - end - - if stat then - for k,v in pairs(self.children) do - if v.default then - self.map:set(section, v.option, v.default) - end - end - - for k,v in pairs(self.defaults) do - self.map:set(section, k, v) - end - end - - self.map.proceed = true - - return stat -end - - -SimpleSection = class(AbstractSection) - -function SimpleSection.__init__(self, form, ...) - AbstractSection.__init__(self, form, nil, ...) - self.template = "cbi/nullsection" -end - - -Table = class(AbstractSection) - -function Table.__init__(self, form, data, ...) - local datasource = {} - local tself = self - datasource.config = "table" - self.data = data or {} - - datasource.formvalue = Map.formvalue - datasource.formvaluetable = Map.formvaluetable - datasource.readinput = true - - function datasource.get(self, section, option) - return tself.data[section] and tself.data[section][option] - end - - function datasource.submitstate(self) - return Map.formvalue(self, "cbi.submit") - end - - function datasource.del(...) - return true - end - - function datasource.get_scheme() - return nil - end - - AbstractSection.__init__(self, datasource, "table", ...) - self.template = "cbi/tblsection" - self.rowcolors = true - self.anonymous = true -end - -function Table.parse(self, readinput) - self.map.readinput = (readinput ~= false) - for i, k in ipairs(self:cfgsections()) do - if self.map:submitstate() then - Node.parse(self, k) - end - end -end - -function Table.cfgsections(self) - local sections = {} - - for i, v in luci.util.kspairs(self.data) do - table.insert(sections, i) - end - - return sections -end - -function Table.update(self, data) - self.data = data -end - - - ---[[ -NamedSection - A fixed configuration section defined by its name -]]-- -NamedSection = class(AbstractSection) - -function NamedSection.__init__(self, map, section, stype, ...) - AbstractSection.__init__(self, map, stype, ...) - - -- Defaults - self.addremove = false - self.template = "cbi/nsection" - self.section = section -end - -function NamedSection.parse(self, novld) - local s = self.section - local active = self:cfgvalue(s) - - if self.addremove then - local path = self.config.."."..s - if active then -- Remove the section - if self.map:formvalue("cbi.rns."..path) and self:remove(s) then - self:push_events() - return - end - else -- Create and apply default values - if self.map:formvalue("cbi.cns."..path) then - self:create(s) - return - end - end - end - - if active then - AbstractSection.parse_dynamic(self, s) - if self.map:submitstate() then - Node.parse(self, s) - end - AbstractSection.parse_optionals(self, s) - - if self.changed then - self:push_events() - end - end -end - - ---[[ -TypedSection - A (set of) configuration section(s) defined by the type - addremove: Defines whether the user can add/remove sections of this type - anonymous: Allow creating anonymous sections - validate: a validation function returning nil if the section is invalid -]]-- -TypedSection = class(AbstractSection) - -function TypedSection.__init__(self, map, type, ...) - AbstractSection.__init__(self, map, type, ...) - - self.template = "cbi/tsection" - self.deps = {} - self.anonymous = false -end - --- Return all matching UCI sections for this TypedSection -function TypedSection.cfgsections(self) - local sections = {} - self.map.uci:foreach(self.map.config, self.sectiontype, - function (section) - if self:checkscope(section[".name"]) then - table.insert(sections, section[".name"]) - end - end) - - return sections -end - --- Limits scope to sections that have certain option => value pairs -function TypedSection.depends(self, option, value) - table.insert(self.deps, {option=option, value=value}) -end - -function TypedSection.parse(self, novld) - if self.addremove then - -- Remove - local crval = REMOVE_PREFIX .. self.config - local name = self.map:formvaluetable(crval) - for k,v in pairs(name) do - if k:sub(-2) == ".x" then - k = k:sub(1, #k - 2) - end - if self:cfgvalue(k) and self:checkscope(k) then - self:remove(k) - end - end - end - - local co - for i, k in ipairs(self:cfgsections()) do - AbstractSection.parse_dynamic(self, k) - if self.map:submitstate() then - Node.parse(self, k, novld) - end - AbstractSection.parse_optionals(self, k) - end - - if self.addremove then - -- Create - local created - local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype - local origin, name = next(self.map:formvaluetable(crval)) - if self.anonymous then - if name then - created = self:create(nil, origin) - end - else - if name then - -- Ignore if it already exists - if self:cfgvalue(name) then - name = nil; - end - - name = self:checkscope(name) - - if not name then - self.err_invalid = true - end - - if name and #name > 0 then - created = self:create(name, origin) and name - if not created then - self.invalid_cts = true - end - end - end - end - - if created then - AbstractSection.parse_optionals(self, created) - end - end - - if self.sortable then - local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype - local order = self.map:formvalue(stval) - if order and #order > 0 then - local sid - local num = 0 - for sid in util.imatch(order) do - self.map.uci:reorder(self.config, sid, num) - num = num + 1 - end - self.changed = (num > 0) - end - end - - if created or self.changed then - self:push_events() - end -end - --- Verifies scope of sections -function TypedSection.checkscope(self, section) - -- Check if we are not excluded - if self.filter and not self:filter(section) then - return nil - end - - -- Check if at least one dependency is met - if #self.deps > 0 and self:cfgvalue(section) then - local stat = false - - for k, v in ipairs(self.deps) do - if self:cfgvalue(section)[v.option] == v.value then - stat = true - end - end - - if not stat then - return nil - end - end - - return self:validate(section) -end - - --- Dummy validate function -function TypedSection.validate(self, section) - return section -end - - ---[[ -AbstractValue - An abstract Value Type - null: Value can be empty - valid: A function returning the value if it is valid otherwise nil - depends: A table of option => value pairs of which one must be true - default: The default value - size: The size of the input fields - rmempty: Unset value if empty - optional: This value is optional (see AbstractSection.optionals) -]]-- -AbstractValue = class(Node) - -function AbstractValue.__init__(self, map, section, option, ...) - Node.__init__(self, ...) - self.section = section - self.option = option - self.map = map - self.config = map.config - self.tag_invalid = {} - self.tag_missing = {} - self.tag_reqerror = {} - self.tag_error = {} - self.deps = {} - self.subdeps = {} - --self.cast = "string" - - self.track_missing = false - self.rmempty = true - self.default = nil - self.size = nil - self.optional = false -end - -function AbstractValue.prepare(self) - self.cast = self.cast or "string" -end - --- Add a dependencie to another section field -function AbstractValue.depends(self, field, value) - local deps - if type(field) == "string" then - deps = {} - deps[field] = value - else - deps = field - end - - table.insert(self.deps, {deps=deps, add=""}) -end - --- Generates the unique CBID -function AbstractValue.cbid(self, section) - return "cbid."..self.map.config.."."..section.."."..self.option -end - --- Return whether this object should be created -function AbstractValue.formcreated(self, section) - local key = "cbi.opt."..self.config.."."..section - return (self.map:formvalue(key) == self.option) -end - --- Returns the formvalue for this object -function AbstractValue.formvalue(self, section) - return self.map:formvalue(self:cbid(section)) -end - -function AbstractValue.additional(self, value) - self.optional = value -end - -function AbstractValue.mandatory(self, value) - self.rmempty = not value -end - -function AbstractValue.add_error(self, section, type, msg) - self.error = self.error or { } - self.error[section] = msg or type - - self.section.error = self.section.error or { } - self.section.error[section] = self.section.error[section] or { } - table.insert(self.section.error[section], msg or type) - - if type == "invalid" then - self.tag_invalid[section] = true - elseif type == "missing" then - self.tag_missing[section] = true - end - - self.tag_error[section] = true - self.map.save = false -end - -function AbstractValue.parse(self, section, novld) - local fvalue = self:formvalue(section) - local cvalue = self:cfgvalue(section) - - -- If favlue and cvalue are both tables and have the same content - -- make them identical - if type(fvalue) == "table" and type(cvalue) == "table" then - local equal = #fvalue == #cvalue - if equal then - for i=1, #fvalue do - if cvalue[i] ~= fvalue[i] then - equal = false - end - end - end - if equal then - fvalue = cvalue - end - end - - if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI - local val_err - fvalue, val_err = self:validate(fvalue, section) - fvalue = self:transform(fvalue) - - if not fvalue and not novld then - self:add_error(section, "invalid", val_err) - end - - if fvalue and (self.forcewrite or not (fvalue == cvalue)) then - if self:write(section, fvalue) then - -- Push events - self.section.changed = true - --luci.util.append(self.map.events, self.events) - end - end - else -- Unset the UCI or error - if self.rmempty or self.optional then - if self:remove(section) then - -- Push events - self.section.changed = true - --luci.util.append(self.map.events, self.events) - end - elseif cvalue ~= fvalue and not novld then - -- trigger validator with nil value to get custom user error msg. - local _, val_err = self:validate(nil, section) - self:add_error(section, "missing", val_err) - end - end -end - --- Render if this value exists or if it is mandatory -function AbstractValue.render(self, s, scope) - if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then - scope = scope or {} - scope.section = s - scope.cbid = self:cbid(s) - Node.render(self, scope) - end -end - --- Return the UCI value of this object -function AbstractValue.cfgvalue(self, section) - local value - if self.tag_error[section] then - value = self:formvalue(section) - else - value = self.map:get(section, self.option) - end - - if not value then - return nil - elseif not self.cast or self.cast == type(value) then - return value - elseif self.cast == "string" then - if type(value) == "table" then - return value[1] - end - elseif self.cast == "table" then - return { value } - end -end - --- Validate the form value -function AbstractValue.validate(self, value) - if self.datatype and value then - if type(value) == "table" then - local v - for _, v in ipairs(value) do - if v and #v > 0 and not verify_datatype(self.datatype, v) then - return nil - end - end - else - if not verify_datatype(self.datatype, value) then - return nil - end - end - end - - return value -end - -AbstractValue.transform = AbstractValue.validate - - --- Write to UCI -function AbstractValue.write(self, section, value) - return self.map:set(section, self.option, value) -end - --- Remove from UCI -function AbstractValue.remove(self, section) - return self.map:del(section, self.option) -end - - - - ---[[ -Value - A one-line value - maxlength: The maximum length -]]-- -Value = class(AbstractValue) - -function Value.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/value" - self.keylist = {} - self.vallist = {} -end - -function Value.reset_values(self) - self.keylist = {} - self.vallist = {} -end - -function Value.value(self, key, val) - val = val or key - table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) -end - - --- DummyValue - This does nothing except being there -DummyValue = class(AbstractValue) - -function DummyValue.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/dvalue" - self.value = nil -end - -function DummyValue.cfgvalue(self, section) - local value - if self.value then - if type(self.value) == "function" then - value = self:value(section) - else - value = self.value - end - else - value = AbstractValue.cfgvalue(self, section) - end - return value -end - -function DummyValue.parse(self) - -end - - ---[[ -Flag - A flag being enabled or disabled -]]-- -Flag = class(AbstractValue) - -function Flag.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/fvalue" - - self.enabled = "1" - self.disabled = "0" - self.default = self.disabled -end - --- A flag can only have two states: set or unset -function Flag.parse(self, section) - local fexists = self.map:formvalue( - FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option) - - if fexists then - local fvalue = self:formvalue(section) and self.enabled or self.disabled - if fvalue ~= self.default or (not self.optional and not self.rmempty) then - self:write(section, fvalue) - else - self:remove(section) - end - else - self:remove(section) - end -end - -function Flag.cfgvalue(self, section) - return AbstractValue.cfgvalue(self, section) or self.default -end - - ---[[ -ListValue - A one-line value predefined in a list - widget: The widget that will be used (select, radio) -]]-- -ListValue = class(AbstractValue) - -function ListValue.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/lvalue" - - self.keylist = {} - self.vallist = {} - self.size = 1 - self.widget = "select" -end - -function ListValue.reset_values(self) - self.keylist = {} - self.vallist = {} -end - -function ListValue.value(self, key, val, ...) - if luci.util.contains(self.keylist, key) then - return - end - - val = val or key - table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) - - for i, deps in ipairs({...}) do - self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps} - end -end - -function ListValue.validate(self, val) - if luci.util.contains(self.keylist, val) then - return val - else - return nil - end -end - - - ---[[ -MultiValue - Multiple delimited values - widget: The widget that will be used (select, checkbox) - delimiter: The delimiter that will separate the values (default: " ") -]]-- -MultiValue = class(AbstractValue) - -function MultiValue.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/mvalue" - - self.keylist = {} - self.vallist = {} - - self.widget = "checkbox" - self.delimiter = " " -end - -function MultiValue.render(self, ...) - if self.widget == "select" and not self.size then - self.size = #self.vallist - end - - AbstractValue.render(self, ...) -end - -function MultiValue.reset_values(self) - self.keylist = {} - self.vallist = {} -end - -function MultiValue.value(self, key, val) - if luci.util.contains(self.keylist, key) then - return - end - - val = val or key - table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) -end - -function MultiValue.valuelist(self, section) - local val = self:cfgvalue(section) - - if not(type(val) == "string") then - return {} - end - - return luci.util.split(val, self.delimiter) -end - -function MultiValue.validate(self, val) - val = (type(val) == "table") and val or {val} - - local result - - for i, value in ipairs(val) do - if luci.util.contains(self.keylist, value) then - result = result and (result .. self.delimiter .. value) or value - end - end - - return result -end - - -StaticList = class(MultiValue) - -function StaticList.__init__(self, ...) - MultiValue.__init__(self, ...) - self.cast = "table" - self.valuelist = self.cfgvalue - - if not self.override_scheme - and self.map:get_scheme(self.section.sectiontype, self.option) then - local vs = self.map:get_scheme(self.section.sectiontype, self.option) - if self.value and vs.values and not self.override_values then - for k, v in pairs(vs.values) do - self:value(k, v) - end - end - end -end - -function StaticList.validate(self, value) - value = (type(value) == "table") and value or {value} - - local valid = {} - for i, v in ipairs(value) do - if luci.util.contains(self.keylist, v) then - table.insert(valid, v) - end - end - return valid -end - - -DynamicList = class(AbstractValue) - -function DynamicList.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/dynlist" - self.cast = "table" - self.keylist = {} - self.vallist = {} -end - -function DynamicList.reset_values(self) - self.keylist = {} - self.vallist = {} -end - -function DynamicList.value(self, key, val) - val = val or key - table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) -end - -function DynamicList.write(self, section, value) - local t = { } - - if type(value) == "table" then - local x - for _, x in ipairs(value) do - if x and #x > 0 then - t[#t+1] = x - end - end - else - t = { value } - end - - if self.cast == "string" then - value = table.concat(t, " ") - else - value = t - end - - return AbstractValue.write(self, section, value) -end - -function DynamicList.cfgvalue(self, section) - local value = AbstractValue.cfgvalue(self, section) - - if type(value) == "string" then - local x - local t = { } - for x in value:gmatch("%S+") do - if #x > 0 then - t[#t+1] = x - end - end - value = t - end - - return value -end - -function DynamicList.formvalue(self, section) - local value = AbstractValue.formvalue(self, section) - - if type(value) == "string" then - if self.cast == "string" then - local x - local t = { } - for x in value:gmatch("%S+") do - t[#t+1] = x - end - value = t - else - value = { value } - end - end - - return value -end - - ---[[ -TextValue - A multi-line value - rows: Rows -]]-- -TextValue = class(AbstractValue) - -function TextValue.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/tvalue" -end - ---[[ -Button -]]-- -Button = class(AbstractValue) - -function Button.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/button" - self.inputstyle = nil - self.rmempty = true -end - - -FileUpload = class(AbstractValue) - -function FileUpload.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/upload" - if not self.map.upload_fields then - self.map.upload_fields = { self } - else - self.map.upload_fields[#self.map.upload_fields+1] = self - end -end - -function FileUpload.formcreated(self, section) - return AbstractValue.formcreated(self, section) or - self.map:formvalue("cbi.rlf."..section.."."..self.option) or - self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") -end - -function FileUpload.cfgvalue(self, section) - local val = AbstractValue.cfgvalue(self, section) - if val and fs.access(val) then - return val - end - return nil -end - -function FileUpload.formvalue(self, section) - local val = AbstractValue.formvalue(self, section) - if val then - if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and - not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") - then - return val - end - fs.unlink(val) - self.value = nil - end - return nil -end - -function FileUpload.remove(self, section) - local val = AbstractValue.formvalue(self, section) - if val and fs.access(val) then fs.unlink(val) end - return AbstractValue.remove(self, section) -end - - -FileBrowser = class(AbstractValue) - -function FileBrowser.__init__(self, ...) - AbstractValue.__init__(self, ...) - self.template = "cbi/browser" -end diff --git a/modules/base/luasrc/luasrc/cbi/datatypes.lua b/modules/base/luasrc/luasrc/cbi/datatypes.lua deleted file mode 100644 index c5f4ec0f0d..0000000000 --- a/modules/base/luasrc/luasrc/cbi/datatypes.lua +++ /dev/null @@ -1,345 +0,0 @@ ---[[ - -LuCI - Configuration Bind Interface - Datatype Tests -(c) 2010 Jo-Philipp Wich - -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 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 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) - return hostname(val) or ipaddr(val) -end - -function network(val) - return uciname(val) or host(val) -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 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 diff --git a/modules/base/luasrc/luasrc/config.lua b/modules/base/luasrc/luasrc/config.lua deleted file mode 100644 index 53db82b322..0000000000 --- a/modules/base/luasrc/luasrc/config.lua +++ /dev/null @@ -1,42 +0,0 @@ ---[[ -LuCI - Configuration - -Description: -Some LuCI configuration values read from uci file "luci" - - -FileId: -$Id$ - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- - -local util = require "luci.util" -module("luci.config", - function(m) - if pcall(require, "luci.model.uci") then - local config = util.threadlocal() - setmetatable(m, { - __index = function(tbl, key) - if not config[key] then - config[key] = luci.model.uci.cursor():get_all("luci", key) - end - return config[key] - end - }) - end - end) diff --git a/modules/base/luasrc/luasrc/dispatcher.lua b/modules/base/luasrc/luasrc/dispatcher.lua deleted file mode 100644 index 9e5b78d5e9..0000000000 --- a/modules/base/luasrc/luasrc/dispatcher.lua +++ /dev/null @@ -1,959 +0,0 @@ ---[[ -LuCI - Dispatcher - -Description: -The request dispatcher and module dispatcher generators - -FileId: -$Id$ - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- - ---- LuCI web dispatcher. -local fs = require "nixio.fs" -local sys = require "luci.sys" -local init = require "luci.init" -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 - - ---- Build the URL relative to the server webroot from given virtual path. --- @param ... Virtual path --- @return Relative URL -function build_url(...) - local path = {...} - local url = { http.getenv("SCRIPT_NAME") or "" } - - local k, v - for k, v in pairs(context.urltoken) do - url[#url+1] = "/;" - url[#url+1] = http.urlencode(k) - url[#url+1] = "=" - url[#url+1] = http.urlencode(v) - end - - 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 - - return table.concat(url, "") -end - ---- Check whether a dispatch node shall be visible --- @param node Dispatch node --- @return Boolean indicating whether the node should be visible -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 - ---- Return a sorted table of visible childs within a given node --- @param node Dispatch node --- @return Ordered table of child node names -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 - - ---- Send a 404 error code and render the "error404" template if available. --- @param message Custom error message (optional) --- @return false -function error404(message) - luci.http.status(404, "Not Found") - message = message or "Not Found" - - require("luci.template") - if not luci.util.copcall(luci.template.render, "error404") then - luci.http.prepare_content("text/plain") - luci.http.write(message) - end - return false -end - ---- Send a 500 error code and render the "error500" template if available. --- @param message Custom error message (optional)# --- @return false -function error500(message) - luci.util.perror(message) - if not context.template_header_sent then - luci.http.status(500, "Internal Server Error") - luci.http.prepare_content("text/plain") - luci.http.write(message) - else - require("luci.template") - if not luci.util.copcall(luci.template.render, "error500", {message=message}) then - luci.http.prepare_content("text/plain") - luci.http.write(message) - end - end - return false -end - -function authenticator.htmlauth(validator, accs, default) - local user = luci.http.formvalue("username") - local pass = luci.http.formvalue("password") - - if user and validator(user, pass) then - return user - end - - require("luci.i18n") - require("luci.template") - context.path = {} - luci.template.render("sysauth", {duser=default, fuser=user}) - return false - -end - ---- Dispatch an HTTP request. --- @param request LuCI HTTP Request object -function httpdispatch(request, prefix) - luci.http.context.request = request - - local r = {} - context.request = r - context.urltoken = {} - - local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) - - if prefix then - for _, node in ipairs(prefix) do - r[#r+1] = node - end - end - - local tokensok = true - for node in pathinfo:gmatch("[^/]+") do - local tkey, tval - if tokensok then - tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)") - end - if tkey then - context.urltoken[tkey] = tval - else - tokensok = false - r[#r+1] = node - end - end - - local stat, err = util.coxpcall(function() - dispatch(context.request) - end, error500) - - luci.http.close() - - --context._disable_memtrace() -end - ---- Dispatches a LuCI virtual path. --- @param request Virtual path -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 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 - require "luci.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 token = ctx.urltoken - 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 - return string.format( - ' %s="%s"', tostring(key), - luci.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 = luci.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; - }, {__index=function(table, key) - if key == "controller" then - return build_url() - elseif key == "REQUEST_URI" then - return build_url(unpack(ctx.requestpath)) - 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 " .. - "http://luci.subsignal.org/trac/newticket" - ) - - if track.sysauth then - local sauth = require "luci.sauth" - - 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 - local verifytoken = false - if not sess then - sess = luci.http.getcookie("sysauth") - sess = sess and sess:match("^[a-f0-9]*$") - verifytoken = true - end - - local sdat = sauth.read(sess) - local user - - if sdat then - if not verifytoken or ctx.urltoken.stok == sdat.token then - user = sdat.user - end - else - local eu = http.getenv("HTTP_AUTH_USER") - local ep = http.getenv("HTTP_AUTH_PASS") - if eu and ep and luci.sys.user.checkpasswd(eu, ep) then - authen = function() return eu end - end - end - - if not util.contains(accs, user) then - if authen then - ctx.urltoken.stok = nil - local user, sess = authen(luci.sys.user.checkpasswd, accs, def) - if not user or not util.contains(accs, user) then - return - else - local sid = sess or luci.sys.uniqueid(16) - if not sess then - local token = luci.sys.uniqueid(16) - sauth.reap() - sauth.write(sid, { - user=user, - token=token, - secret=luci.sys.uniqueid(16) - }) - ctx.urltoken.stok = token - end - luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path="..build_url()) - ctx.authsession = sid - ctx.authuser = user - end - else - luci.http.status(403, "Forbidden") - return - end - else - ctx.authsession = sess - ctx.authuser = user - end - end - - if track.setgroup then - luci.sys.process.setgroup(track.setgroup) - end - - if track.setuser then - luci.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 - ---- Generate the dispatching index using the best possible strategy. -function createindex() - local path = luci.util.libpath() .. "/controller/" - local suff = { ".lua", ".lua.gz" } - - if luci.util.copcall(require, "luci.fastindex") then - createindex_fastindex(path, suff) - else - createindex_plain(path, suff) - end -end - ---- Generate the dispatching index using the fastindex C-indexer. --- @param path Controller base directory --- @param suffixes Controller file suffixes -function createindex_fastindex(path, suffixes) - index = {} - - if not fi then - fi = luci.fastindex.new("index") - for _, suffix in ipairs(suffixes) do - fi.add(path .. "*" .. suffix) - fi.add(path .. "*/*" .. suffix) - end - end - fi.scan() - - for k, v in pairs(fi.indexes) do - index[v[2]] = v[1] - end -end - ---- Generate the dispatching index using the native file-cache based strategy. --- @param path Controller base directory --- @param suffixes Controller file suffixes -function createindex_plain(path, suffixes) - local controllers = { } - for _, suffix in ipairs(suffixes) do - nixio.util.consume((fs.glob(path .. "*" .. suffix)), controllers) - nixio.util.consume((fs.glob(path .. "*/*" .. suffix)), controllers) - 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 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 i,c in ipairs(controllers) do - local modname = "luci.controller." .. c:sub(#path+1, #c):gsub("/", ".") - for _, suffix in ipairs(suffixes) do - modname = modname:gsub(suffix.."$", "") - end - - local mod = require(modname) - assert(mod ~= true, - "Invalid controller file found\n" .. - "The file '" .. c .. "' 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 '" .. c .. "' 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 - ---- Create the dispatching tree from the index. --- 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 - ---- Register a tree modifier. --- @param func Modifier function --- @param order Modifier order value (optional) -function modifier(func, order) - context.modifiers[#context.modifiers+1] = { - func = func, - order = order or 0, - module - = getfenv(2)._NAME - } -end - ---- Clone a node of the dispatching tree to another position. --- @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 -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 - ---- Create a new dispatching node and define common parameters. --- @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 -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 - ---- Fetch or create a dispatching node without setting the target module or --- enabling the node. --- @param ... Virtual path --- @return Dispatching tree node -function get(...) - return _create_node({...}) -end - ---- Fetch or create a new dispatching node. --- @param ... Virtual path --- @return Dispatching tree node -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 - ---- Alias the first (lowest order) page automatically -function firstchild() - return { type = "firstchild", target = _firstchild } -end - ---- Create a redirect to another dispatching node. --- @param ... Virtual path destination -function alias(...) - local req = {...} - return function(...) - for _, r in ipairs({...}) do - req[#req+1] = r - end - - dispatch(req) - end -end - ---- Rewrite the first x path values of the request. --- @param n Number of path values to replace --- @param ... Virtual path to replace removed path values with -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 - ---- Create a function-call dispatching target. --- @param name Target function of local controller --- @param ... Additional parameters passed to the function -function call(name, ...) - return {type = "call", argv = {...}, name = name, target = _call} -end - - -local _template = function(self, ...) - require "luci.template".render(self.view) -end - ---- Create a template render dispatching target. --- @param name Template to be rendered -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 - ---- Create a CBI model dispatching target. --- @param model CBI model to be rendered -function cbi(model, config) - return {type = "cbi", 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 - ---- Create a combined dispatching target for non argv and argv requests. --- @param trg1 Overview Target --- @param trg2 Detail Target -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 - ---- Create a CBI form model dispatching target. --- @param model CBI form model tpo be rendered -function form(model) - return {type = "cbi", model = model, target = _form} -end - ---- Access the luci.i18n translate() api. --- @class function --- @name translate --- @param text Text to translate -translate = i18n.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. -function _(text) - return text -end diff --git a/modules/base/luasrc/luasrc/http.lua b/modules/base/luasrc/luasrc/http.lua deleted file mode 100644 index c53307a5a1..0000000000 --- a/modules/base/luasrc/luasrc/http.lua +++ /dev/null @@ -1,344 +0,0 @@ ---[[ -LuCI - HTTP-Interaction - -Description: -HTTP-Header manipulator and form variable preprocessor - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- - -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 - ---- LuCI Web Framework high-level HTTP functions. -module "luci.http" - -context = util.threadlocal() - -Request = util.class() -function Request.__init__(self, env, sourcein, sinkerr) - self.input = sourcein - self.error = sinkerr - - - -- File handler - self.filehandler = function() end - - -- 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 -end - -function Request._parse_input(self) - protocol.parse_message_body( - self.input, - self.message, - self.filehandler - ) - self.parsed_input = true -end - ---- Close the HTTP-Connection. -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 - ---- Return the request content if the request was of unknown type. --- @return HTTP request body --- @return HTTP request body length -function content() - return context.request:content() -end - ---- Get a certain HTTP input value or a table of all input values. --- @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 -function formvalue(name, noparse) - return context.request:formvalue(name, noparse) -end - ---- Get a table of all HTTP input values with a certain prefix. --- @param prefix Prefix --- @return Table of all HTTP input values with given prefix -function formvaluetable(prefix) - return context.request:formvaluetable(prefix) -end - ---- Get the value of a certain HTTP-Cookie. --- @param name Cookie Name --- @return String containing cookie data -function getcookie(name) - return context.request:getcookie(name) -end - ---- Get the value of a certain HTTP environment variable --- or the environment table itself. --- @param name Environment variable --- @return HTTP environment value or environment table -function getenv(name) - return context.request:getenv(name) -end - ---- Set a handler function for incoming user file uploads. --- @param callback Handler function -function setfilehandler(callback) - return context.request:setfilehandler(callback) -end - ---- Send a HTTP-Header. --- @param key Header key --- @param value Header value -function header(key, value) - if not context.headers then - context.headers = {} - end - context.headers[key:lower()] = value - coroutine.yield(2, key, value) -end - ---- Set the mime type of following content data. --- @param mime Mimetype of following content -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 - ---- Get the RAW HTTP input source --- @return HTTP LTN12 source -function source() - return context.request.input -end - ---- Set the HTTP status code and status message. --- @param code Status code --- @param message Status message -function status(code, message) - code = code or 200 - message = message or "OK" - context.status = code - coroutine.yield(1, code, message) -end - ---- 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. --- @param content Content chunk --- @param src_err Error object from source (optional) --- @see 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 - ---- Splice data from a filedescriptor to the client. --- @param fp File descriptor --- @param size Bytes to splice (optional) -function splice(fd, size) - coroutine.yield(6, fd, size) -end - ---- Redirects the client to a new URL and closes the connection. --- @param url Target URL -function redirect(url) - status(302, "Found") - header("Location", url) - close() -end - ---- Create a querystring out of a table of key - value pairs. --- @param table Query string source table --- @return Encoded HTTP query string -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 - ---- 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 -urldecode = protocol.urldecode - ---- Return the URL-encoded equivalent of a string. --- @param str Source string --- @return URL-encoded string --- @see urldecode -urlencode = protocol.urlencode - ---- Send the given data as JSON encoded string. --- @param data Data to send -function write_json(x) - if x == nil then - write("null") - elseif type(x) == "table" then - local k, v - if type(next(x)) == "number" then - write("[ ") - for k, v in ipairs(x) do - write_json(v) - if next(x, k) then - write(", ") - end - end - write(" ]") - else - write("{ ") - for k, v in pairs(x) do - write("%q: " % k) - write_json(v) - if next(x, k) then - write(", ") - end - end - write(" }") - end - elseif type(x) == "number" or type(x) == "boolean" then - if (x ~= x) then - -- NaN is the only value that doesn't equal to itself. - write("Number.NaN") - else - write(tostring(x)) - end - else - write('"%s"' % tostring(x):gsub('["%z\1-\31]', function(c) - return '\\u%04x' % c:byte(1) - end)) - end -end diff --git a/modules/base/luasrc/luasrc/http/protocol.lua b/modules/base/luasrc/luasrc/http/protocol.lua deleted file mode 100644 index 0d41550b23..0000000000 --- a/modules/base/luasrc/luasrc/http/protocol.lua +++ /dev/null @@ -1,688 +0,0 @@ ---[[ - -HTTP protocol implementation for LuCI -(c) 2008 Freifunk Leipzig / Jo-Philipp Wich - -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$ - -]]-- - ---- 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", package.seeall) - -local ltn12 = require("luci.ltn12") - -HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size - ---- Decode an urlencoded string - optionally without decoding --- the "+" sign to " " - and return the decoded string. --- @param str Input string in x-www-urlencoded format --- @param no_plus Don't decode "+" signs to spaces --- @return The decoded string --- @see urlencode -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 - ---- 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. --- @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 -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 - ---- Encode given string to x-www-urlencoded format. --- @param str String to encode --- @return String containing the encoded data --- @see urldecode -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 - ---- 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. --- @param tbl Table with the values --- @return String containing encoded values --- @see urldecode_params -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. --- @param tbl Table where parameter should be created --- @param key Parameter name --- @return Always nil -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) --- 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. --- @param tbl Table containing the previously initialized parameter value --- @param key Parameter name --- @param chunk String containing the data to append --- @return Always nil --- @see __initval -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. --- @param tbl Table containing the previously initialized parameter value --- @param key Parameter name --- @param handler Function which transforms the parameter value --- @return Always nil --- @see __initval --- @see __appendval -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 - - ---- 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. --- @param sock Readable network socket --- @return Ltn12 source function -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 - ---- 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) --- @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 -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 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 - ---- 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. --- @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 -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 - ---- Try to extract an http message header including information like protocol --- version, message headers and resulting CGI environment variables from the --- given ltn12 source. --- @param src Ltn12 source function --- @return HTTP message object --- @see parse_message_body -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 - ---- 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. --- @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 -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 - sink = filecb - - -- ... else append to .content - else - msg.content = "" - msg.content_length = 0 - - sink = function( chunk, err ) - 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 err then - return true - end - end - - return true - end -end - ---- Table containing human readable messages for several http status codes. --- @class table -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", -} diff --git a/modules/base/luasrc/luasrc/http/protocol/conditionals.lua b/modules/base/luasrc/luasrc/http/protocol/conditionals.lua deleted file mode 100644 index 75e1f7b37c..0000000000 --- a/modules/base/luasrc/luasrc/http/protocol/conditionals.lua +++ /dev/null @@ -1,153 +0,0 @@ ---[[ - -HTTP protocol implementation for LuCI - RFC2616 / 14.19, 14.24 - 14.28 -(c) 2008 Freifunk Leipzig / Jo-Philipp Wich - -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$ - -]]-- - ---- 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", package.seeall) - -local date = require("luci.http.protocol.date") - - ---- Implement 14.19 / ETag. --- @param stat A file.stat structure --- @return String containing the generated tag suitable for ETag headers -function mk_etag( stat ) - if stat ~= nil then - return string.format( '"%x-%x-%x"', stat.ino, stat.size, stat.mtime ) - end -end - ---- 14.24 / If-Match --- Test whether the given message object contains an "If-Match" header and --- compare it against the given stat object. --- @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 -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 - ---- 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. --- @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 -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 - ---- 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. --- @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 -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 - ---- 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. --- @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 -function if_range( req, stat ) - -- Sorry, no subranges (yet) - return false, 412 -end - ---- 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. --- @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 -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 diff --git a/modules/base/luasrc/luasrc/http/protocol/date.lua b/modules/base/luasrc/luasrc/http/protocol/date.lua deleted file mode 100644 index 83d11e2c25..0000000000 --- a/modules/base/luasrc/luasrc/http/protocol/date.lua +++ /dev/null @@ -1,115 +0,0 @@ ---[[ - -HTTP protocol implementation for LuCI - date handling -(c) 2008 Freifunk Leipzig / Jo-Philipp Wich - -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$ - -]]-- - ---- LuCI http protocol implementation - date helper class. --- 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" -} - ---- Return the time offset in seconds between the UTC and given time zone. --- @param tz Symbolic or numeric timezone specifier --- @return Time offset to UTC in seconds -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 - ---- Parse given HTTP date string and convert it to unix epoch time. --- @param data String containing the date --- @return Unix epoch time -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 - ---- Convert the given unix epoch time to valid HTTP date string. --- @param time Unix epoch time --- @return String containing the formatted date -function to_http(time) - return os.date( "%a, %d %b %Y %H:%M:%S GMT", time ) -end - ---- Compare two dates which can either be unix epoch times or HTTP date strings. --- @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 -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 diff --git a/modules/base/luasrc/luasrc/http/protocol/mime.lua b/modules/base/luasrc/luasrc/http/protocol/mime.lua deleted file mode 100644 index c878160664..0000000000 --- a/modules/base/luasrc/luasrc/http/protocol/mime.lua +++ /dev/null @@ -1,99 +0,0 @@ ---[[ - -HTTP protocol implementation for LuCI - mime handling -(c) 2008 Freifunk Leipzig / Jo-Philipp Wich - -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$ - -]]-- - ---- 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", package.seeall) - -require("luci.util") - ---- MIME mapping table containg extension - mimetype relations. --- @class table -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"; -} - ---- Extract extension from a filename and return corresponding mime-type or --- "application/octet-stream" if the extension is unknown. --- @param filename The filename for which the mime type is guessed --- @return String containign the determined mime type -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 - ---- Return corresponding extension for a given mime type or nil if the --- given mime-type is unknown. --- @param mimetype The mimetype to retrieve the extension from --- @return String with the extension or nil for unknown type -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 diff --git a/modules/base/luasrc/luasrc/i18n.lua b/modules/base/luasrc/luasrc/i18n.lua deleted file mode 100644 index 545a8aed93..0000000000 --- a/modules/base/luasrc/luasrc/i18n.lua +++ /dev/null @@ -1,104 +0,0 @@ ---[[ -LuCI - Internationalisation - -Description: -A very minimalistic but yet effective internationalisation module - -FileId: -$Id$ - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- - ---- LuCI translation library. -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" - ---- Clear the translation table. -function clear() -end - ---- Load a translation and copy its data into the translation table. --- @param file Language file --- @param lang Two-letter language code --- @param force Force reload even if already loaded (optional) --- @return Success status -function load(file, lang, force) -end - ---- Load a translation file using the default translation language. --- Alternatively load the translation of the fallback language. --- @param file Language file --- @param force Force reload even if already loaded (optional) -function loadc(file, force) -end - ---- Set the context default translation language. --- @param lang Two-letter language code -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 - ---- Return the translated value for a specific translation key. --- @param key Default translation text --- @return Translated string -function translate(key) - return tparser.translate(key) or key -end - ---- Return the translated value for a specific translation key and use it as sprintf pattern. --- @param key Default translation text --- @param ... Format parameters --- @return Translated and formatted string -function translatef(key, ...) - return tostring(translate(key)):format(...) -end - ---- 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 tostring(translate(...)) --- @param key Default translation text --- @return Translated string -function string(key) - return tostring(translate(key)) -end - ---- 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 tostring(translatef(...)) --- @param key Default translation text --- @param ... Format parameters --- @return Translated and formatted string -function stringf(key, ...) - return tostring(translate(key)):format(...) -end diff --git a/modules/base/luasrc/luasrc/sauth.lua b/modules/base/luasrc/luasrc/sauth.lua deleted file mode 100644 index 32f172dcda..0000000000 --- a/modules/base/luasrc/luasrc/sauth.lua +++ /dev/null @@ -1,127 +0,0 @@ ---[[ - -Session authentication -(c) 2008 Steven Barth - -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$ - -]]-- - ---- LuCI session library. -module("luci.sauth", package.seeall) -require("luci.util") -require("luci.sys") -require("luci.config") -local nixio = require "nixio", require "nixio.util" -local fs = require "nixio.fs" - - -luci.config.sauth = luci.config.sauth or {} -sessionpath = luci.config.sauth.sessionpath -sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60 - ---- Prepare session storage by creating the session directory. -function prepare() - fs.mkdir(sessionpath, 700) - if not sane() then - error("Security Exception: Session path is not sane!") - end -end - -local function _read(id) - local blob = fs.readfile(sessionpath .. "/" .. id) - return blob -end - -local function _write(id, data) - local f = nixio.open(sessionpath .. "/" .. id, "w", 600) - f:writeall(data) - f:close() -end - -local function _checkid(id) - return not not (id and #id == 32 and id:match("^[a-fA-F0-9]+$")) -end - ---- Write session data to a session file. --- @param id Session identifier --- @param data Session data table -function write(id, data) - if not sane() then - prepare() - end - - assert(_checkid(id), "Security Exception: Session ID is invalid!") - assert(type(data) == "table", "Security Exception: Session data invalid!") - - data.atime = luci.sys.uptime() - - _write(id, luci.util.get_bytecode(data)) -end - ---- Read a session and return its content. --- @param id Session identifier --- @return Session data table or nil if the given id is not found -function read(id) - if not id or #id == 0 then - return nil - end - - assert(_checkid(id), "Security Exception: Session ID is invalid!") - - if not sane(sessionpath .. "/" .. id) then - return nil - end - - local blob = _read(id) - local func = loadstring(blob) - setfenv(func, {}) - - local sess = func() - assert(type(sess) == "table", "Session data invalid!") - - if sess.atime and sess.atime + sessiontime < luci.sys.uptime() then - kill(id) - return nil - end - - -- refresh atime in session - write(id, sess) - - return sess -end - ---- Check whether Session environment is sane. --- @return Boolean status -function sane(file) - return luci.sys.process.info("uid") - == fs.stat(file or sessionpath, "uid") - and fs.stat(file or sessionpath, "modestr") - == (file and "rw-------" or "rwx------") -end - ---- Kills a session --- @param id Session identifier -function kill(id) - assert(_checkid(id), "Security Exception: Session ID is invalid!") - fs.unlink(sessionpath .. "/" .. id) -end - ---- Remove all expired session data files -function reap() - if sane() then - local id - for id in nixio.fs.dir(sessionpath) do - if _checkid(id) then - -- reading the session will kill it if it is expired - read(id) - end - end - end -end diff --git a/modules/base/luasrc/luasrc/template.lua b/modules/base/luasrc/luasrc/template.lua deleted file mode 100644 index 72127d1df1..0000000000 --- a/modules/base/luasrc/luasrc/template.lua +++ /dev/null @@ -1,107 +0,0 @@ ---[[ -LuCI - Template Parser - -Description: -A template parser supporting includes, translations, Lua code blocks -and more. It can be used either as a compiler or as an interpreter. - -FileId: $Id$ - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- - -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 - - --- 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) - - self.template = self.cache[name] - self.name = name - - -- 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 = viewdir .. "/" .. name .. ".htm" - - self.template, _, err = tparser.parse(sourcefile) - - -- 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")) - else - 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 diff --git a/modules/base/luasrc/luasrc/view/cbi/apply_xhr.htm b/modules/base/luasrc/luasrc/view/cbi/apply_xhr.htm deleted file mode 100644 index 1814c9393b..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/apply_xhr.htm +++ /dev/null @@ -1,43 +0,0 @@ -<% export("cbi_apply_xhr", function(id, configs, redirect) -%> -
- <%:Applying changes%> - - - <%:Loading%> - <%:Waiting for changes to be applied...%> -
-<%- end) %> diff --git a/modules/base/luasrc/luasrc/view/cbi/browser.htm b/modules/base/luasrc/luasrc/view/cbi/browser.htm deleted file mode 100644 index e4a4077d55..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/browser.htm +++ /dev/null @@ -1,7 +0,0 @@ -<% local v = self:cfgvalue(section) -%> -<%+cbi/valueheader%> - /> - -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/button.htm b/modules/base/luasrc/luasrc/view/cbi/button.htm deleted file mode 100644 index 30f8ddfda5..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/button.htm +++ /dev/null @@ -1,7 +0,0 @@ -<%+cbi/valueheader%> - <% if self:cfgvalue(section) ~= false then %> - " type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> /> - <% else %> - - - <% end %> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/cell_valuefooter.htm b/modules/base/luasrc/luasrc/view/cbi/cell_valuefooter.htm deleted file mode 100644 index 220ebd42ba..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/cell_valuefooter.htm +++ /dev/null @@ -1,20 +0,0 @@ - -
">
- - -<% if #self.deps > 0 then -%> - -<%- end %> diff --git a/modules/base/luasrc/luasrc/view/cbi/cell_valueheader.htm b/modules/base/luasrc/luasrc/view/cbi/cell_valueheader.htm deleted file mode 100644 index 9e2e145ddb..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/cell_valueheader.htm +++ /dev/null @@ -1,2 +0,0 @@ - -
"> diff --git a/modules/base/luasrc/luasrc/view/cbi/compound.htm b/modules/base/luasrc/luasrc/view/cbi/compound.htm deleted file mode 100644 index 12d02bb1d8..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/compound.htm +++ /dev/null @@ -1 +0,0 @@ -<%- self:render_children() %> diff --git a/modules/base/luasrc/luasrc/view/cbi/delegator.htm b/modules/base/luasrc/luasrc/view/cbi/delegator.htm deleted file mode 100644 index 4fd19265d8..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/delegator.htm +++ /dev/null @@ -1,24 +0,0 @@ -<%- self.active:render() %> -
- -<% for _, x in ipairs(self.chain) do %> - -<% end %> -<% if not self.disallow_pageactions then %> -<% if self.allow_finish and not self:get_next(self.current) then %> - -<% elseif self:get_next(self.current) then %> - -<% end %> -<% if self.allow_cancel then %> - -<% end %> -<% if self.allow_reset then %> - -<% end %> -<% if self.allow_back and self:get_prev(self.current) then %> - -<% end %> -<% end %> - -
diff --git a/modules/base/luasrc/luasrc/view/cbi/dvalue.htm b/modules/base/luasrc/luasrc/view/cbi/dvalue.htm deleted file mode 100644 index 78e6f323d7..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/dvalue.htm +++ /dev/null @@ -1,13 +0,0 @@ -<%+cbi/valueheader%> -<% if self.href then %><% end -%> - <% - local val = self:cfgvalue(section) or self.default or "" - if not self.rawhtml then - write(pcdata(val)) - else - write(val) - end - %> -<%- if self.href then %><%end%> -" /> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/dynlist.htm b/modules/base/luasrc/luasrc/view/cbi/dynlist.htm deleted file mode 100644 index fd626a4ecf..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/dynlist.htm +++ /dev/null @@ -1,26 +0,0 @@ -<%+cbi/valueheader%> -
-<% - local vals = self:cfgvalue(section) or {} - for i=1, #vals + 1 do - local val = vals[i] - if (val and #val > 0) or (i == 1) then -%> - />
-<% end end %> -
- -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/filebrowser.htm b/modules/base/luasrc/luasrc/view/cbi/filebrowser.htm deleted file mode 100644 index a79beebba7..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/filebrowser.htm +++ /dev/null @@ -1,108 +0,0 @@ - - - - - Filebrowser - LuCI - - - - - - <% - require("nixio.fs") - require("nixio.util") - require("luci.http") - require("luci.dispatcher") - - local field = luci.http.formvalue('field') - local request = luci.dispatcher.context.args - local path = { '' } - - for i = 1, #request do - if request[i] ~= '..' and #request[i] > 0 then - path[#path+1] = request[i] - end - end - - local filepath = table.concat( path, '/' ) - local filestat = nixio.fs.stat( filepath ) - local baseurl = luci.dispatcher.build_url('admin', 'filebrowser') - - if filestat and filestat.type == "reg" then - table.remove( path, #path ) - filepath = table.concat( path, '/' ) .. '/' - elseif not ( filestat and filestat.type == "dir" ) then - path = { '' } - filepath = '/' - else - filepath = filepath .. '/' - end - - local entries = nixio.util.consume((nixio.fs.dir(filepath))) - -%> -
- Location: - <% for i, dir in ipairs(path) do %> - <% if i == 1 then %> - (root) - <% elseif next(path, i) then %> - <% baseurl = baseurl .. '/' .. dir %> - / <%=dir%> - <% else %> - <% baseurl = baseurl .. '/' .. dir %> - / <%=dir%> - <% end %> - <% end %> -
- -
- -
-
    - <% for _, e in luci.util.vspairs(entries) do - local stat = nixio.fs.stat(filepath..e) - if stat and stat.type == 'dir' then - -%> -
  • - <%:Directory%> - <%=e%>/ -
  • - <% end end -%> - - <% for _, e in luci.util.vspairs(entries) do - local stat = nixio.fs.stat(filepath..e) - if stat and stat.type ~= 'dir' then - -%> -
  • - <%:File%> - <%=e%> -
  • - <% end end -%> -
-
- - diff --git a/modules/base/luasrc/luasrc/view/cbi/firewall_zoneforwards.htm b/modules/base/luasrc/luasrc/view/cbi/firewall_zoneforwards.htm deleted file mode 100644 index 2a433b5696..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/firewall_zoneforwards.htm +++ /dev/null @@ -1,59 +0,0 @@ -<%+cbi/valueheader%> - -<%- - local utl = require "luci.util" - local fwm = require "luci.model.firewall".init() - local nwm = require "luci.model.network".init() - - local zone, fwd, fz - local value = self:formvalue(section) - if not value or value == "-" then - value = self:cfgvalue(section) or self.default - end - - local def = fwm:get_defaults() - local zone = fwm:get_zone(value) - local empty = true --%> - -<% if zone then %> -
- -  ⇒  - <% for _, fwd in ipairs(zone:get_forwardings_by("src")) do - fz = fwd:dest_zone() - empty = false %> -   - <% end %> - <% if empty then %> - - <% end %> -
-<% end %> - -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/firewall_zonelist.htm b/modules/base/luasrc/luasrc/view/cbi/firewall_zonelist.htm deleted file mode 100644 index 7973437f40..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/firewall_zonelist.htm +++ /dev/null @@ -1,89 +0,0 @@ -<%+cbi/valueheader%> - -<%- - local utl = require "luci.util" - local fwm = require "luci.model.firewall".init() - local nwm = require "luci.model.network".init() - - local zone, net, iface - local zones = fwm:get_zones() - local value = self:formvalue(section) - if not value or value == "-" then - value = self:cfgvalue(section) or self.default - end - - local selected = false - local checked = { } - - for value in utl.imatch(value) do - checked[value] = true - end - - if not next(checked) then - checked[""] = true - end --%> - -
    - <% if self.allowlocal then %> -
  • - />   - style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> - <%:Device%> - <% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %> - -
  • - <% end %> - <% if self.allowany then %> -
  • - />   - style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> - <%:Any zone%> - <% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %> - -
  • - <% end %> - <% - for _, zone in utl.spairs(zones, function(a,b) return (zones[a]:name() < zones[b]:name()) end) do - if zone:name() ~= self.exclude then - selected = selected or (value == zone:name()) - %> -
  • - />   - style="background-color:<%=zone:get_color()%>" class="zonebadge"> - <%=zone:name()%>: - <% - local zempty = true - for _, net in ipairs(zone:get_networks()) do - net = nwm:get_network(net) - if net then - zempty = false - %> - <%=net:name()%>: - <% - local nempty = true - for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do - nempty = false - %> - style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> - <% end %> - <% if nempty then %><%:(empty)%><% end %> - - <% end end %> - <% if zempty then %><%:(empty)%><% end %> - -
  • - <% end end %> - - <% if self.widget ~= "checkbox" and not self.nocreate then %> -
  • - />   -
    - <%:unspecified -or- create:%>  - onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> -
    -
  • - <% end %> -
- -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/footer.htm b/modules/base/luasrc/luasrc/view/cbi/footer.htm deleted file mode 100644 index 2c34028e58..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/footer.htm +++ /dev/null @@ -1,26 +0,0 @@ - <%- if pageaction then -%> -
- <% if redirect then %> -
- -
- <% end %> - - <% if flow.skip then %> - - <% end %> - <% if not autoapply and not flow.hideapplybtn then %> - - <% end %> - <% if not flow.hidesavebtn then %> - - <% end %> - <% if not flow.hideresetbtn then %> - - <% end %> - - -
- <%- end -%> - -<%+footer%> diff --git a/modules/base/luasrc/luasrc/view/cbi/full_valuefooter.htm b/modules/base/luasrc/luasrc/view/cbi/full_valuefooter.htm deleted file mode 100644 index 4876fbcc99..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/full_valuefooter.htm +++ /dev/null @@ -1,59 +0,0 @@ - <% if self.description and #self.description > 0 then -%> - <% if not luci.util.instanceof(self, luci.cbi.DynamicList) and (not luci.util.instanceof(self, luci.cbi.Flag) or self.orientation == "horizontal") then -%> -
- <%- end %> -
- <%:help%> - <%=self.description%> -
- <%- end %> - <%- if self.title and #self.title > 0 then -%> -
- <%- end -%> - - - -<% if #self.deps > 0 or #self.subdeps > 0 then -%> - -<%- end %> diff --git a/modules/base/luasrc/luasrc/view/cbi/full_valueheader.htm b/modules/base/luasrc/luasrc/view/cbi/full_valueheader.htm deleted file mode 100644 index aaf085473a..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/full_valueheader.htm +++ /dev/null @@ -1,9 +0,0 @@ -
"> - <%- if self.title and #self.title > 0 then -%> - -
- <%- end -%> diff --git a/modules/base/luasrc/luasrc/view/cbi/fvalue.htm b/modules/base/luasrc/luasrc/view/cbi/fvalue.htm deleted file mode 100644 index a1e0808e8d..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/fvalue.htm +++ /dev/null @@ -1,9 +0,0 @@ -<%+cbi/valueheader%> - /> - /> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/header.htm b/modules/base/luasrc/luasrc/view/cbi/header.htm deleted file mode 100644 index 2bddaba61a..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/header.htm +++ /dev/null @@ -1,7 +0,0 @@ -<%+header%> -
-
- - - -
diff --git a/modules/base/luasrc/luasrc/view/cbi/lvalue.htm b/modules/base/luasrc/luasrc/view/cbi/lvalue.htm deleted file mode 100644 index 8cc086db42..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/lvalue.htm +++ /dev/null @@ -1,18 +0,0 @@ -<%+cbi/valueheader%> -<% if self.widget == "select" then %> - -<% elseif self.widget == "radio" then - local c = 0 - for i, key in pairs(self.keylist) do - c = c + 1 -%> - /> - ><%=self.vallist[i]%> -<% if c == self.size then c = 0 %><% if self.orientation == "horizontal" then %> <% else %>
<% end %> -<% end end %> -<% end %> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/map.htm b/modules/base/luasrc/luasrc/view/cbi/map.htm deleted file mode 100644 index 053220d185..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/map.htm +++ /dev/null @@ -1,13 +0,0 @@ -<%- if firstmap and messages then local msg; for _, msg in ipairs(messages) do -%> -
<%=pcdata(msg)%>
-<%- end end -%> - -<%-+cbi/apply_xhr-%> - -
- <% if self.title and #self.title > 0 then %>

<%=self.title%>

<% end %> - <% if self.description and #self.description > 0 then %>
<%=self.description%>
<% end %> - <%- if firstmap and applymap then cbi_apply_xhr(self.config, parsechain, redirect) end -%> - <%- self:render_children() %> -
-
diff --git a/modules/base/luasrc/luasrc/view/cbi/mvalue.htm b/modules/base/luasrc/luasrc/view/cbi/mvalue.htm deleted file mode 100644 index 6a0b3881d0..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/mvalue.htm +++ /dev/null @@ -1,19 +0,0 @@ -<% local v = self:valuelist(section) or {} -%> -<%+cbi/valueheader%> -<% if self.widget == "select" then %> - -<% elseif self.widget == "checkbox" then - local c = 0; - for i, key in pairs(self.keylist) do - c = c + 1 -%> - /> - ><%=self.vallist[i]%>
-<% if c == self.size then c = 0 %>
-<% end end %> -<% end %> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/network_ifacelist.htm b/modules/base/luasrc/luasrc/view/cbi/network_ifacelist.htm deleted file mode 100644 index 643d849a50..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/network_ifacelist.htm +++ /dev/null @@ -1,81 +0,0 @@ -<%+cbi/valueheader%> - -<%- - local utl = require "luci.util" - local net = require "luci.model.network".init() - local cbeid = luci.cbi.FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option - - local iface - local ifaces = net:get_interfaces() - local value - - if self.map:formvalue(cbeid) == "1" then - value = self:formvalue(section) or self.default or "" - else - value = self:cfgvalue(section) or self.default - end - - local checked = { } - - if value then - for value in utl.imatch(value) do - checked[value] = true - end - else - local n = self.network and net:get_network(self.network) - if n then - local i - for _, i in ipairs(n:get_interfaces() or { n:get_interface() }) do - checked[i:name()] = true - end - end - end --%> - - -
    - <% for _, iface in ipairs(ifaces) do - local link = iface:adminlink() - if (not self.nobridges or not iface:is_bridge()) and - (not self.noinactive or iface:is_up()) and - iface:name() ~= self.exclude - then %> -
  • - " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "." .. iface:name()) .. - attr("name", cbid) .. attr("value", iface:name()) .. - ifattr(checked[iface:name()], "checked", "checked") - %> />   - > - <% if link then -%><% end -%> - style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> - <% if link then -%><% end -%> - <%=pcdata(iface:get_i18n())%> - <% local ns = iface:get_networks(); if #ns > 0 then %>( - <%- local i, n; for i, n in ipairs(ns) do -%> - <%-= (i>1) and ', ' -%> - <%=n:name()%> - <%- end -%> - )<% end %> - -
  • - <% end end %> - <% if not self.nocreate then %> -
  • - " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "_custom") .. - attr("name", cbid) .. - attr("value", " ") - %> />   - > - - <%:Custom Interface%>: - - -
  • - <% end %> -
- -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/network_netinfo.htm b/modules/base/luasrc/luasrc/view/cbi/network_netinfo.htm deleted file mode 100644 index 4fd84112a4..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/network_netinfo.htm +++ /dev/null @@ -1,27 +0,0 @@ -<%+cbi/valueheader%> - -<%- - local value = self:formvalue(section) - if not value or value == "-" then - value = self:cfgvalue(section) or self.default - end - - local nwm = require "luci.model.network".init() - local net = nwm:get_network(value) --%> - -<% if net then %> -<%=net:name()%>: - <% - local empty = true - for _, iface in ipairs(net:get_interfaces() or { net:get_interface() }) do - if not iface:is_bridge() then - empty = false - %> - style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> - <% end end %> - <% if empty then %><%:(no interfaces attached)%><% end %> - -<% end %> - -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/network_netlist.htm b/modules/base/luasrc/luasrc/view/cbi/network_netlist.htm deleted file mode 100644 index 7e23d149a8..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/network_netlist.htm +++ /dev/null @@ -1,81 +0,0 @@ -<%+cbi/valueheader%> - -<%- - local utl = require "luci.util" - local nwm = require "luci.model.network".init() - - local net, iface - local networks = nwm:get_networks() - local value = self:formvalue(section) - - self.cast = nil - - if not value or value == "-" then - value = self:cfgvalue(section) or self.default - end - - local checked = { } - for value in utl.imatch(value) do - checked[value] = true - end --%> - -
    - <% for _, net in ipairs(networks) do - if (net:name() ~= "loopback") and - (net:name() ~= self.exclude) and - (not self.novirtual or not net:is_virtual()) - then %> -
  • - " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "." .. net:name()) .. - attr("name", cbid) .. attr("value", net:name()) .. - ifattr(checked[net:name()], "checked", "checked") - %> />   - > - <%=net:name()%>: - <% - local empty = true - for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do - if not iface:is_bridge() then - empty = false - %> - style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> - <% end end %> - <% if empty then %><%:(no interfaces attached)%><% end %> - - -
  • - <% end end %> - - <% if not self.nocreate then %> -
  • - " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not value and self.widget ~= "checkbox", "checked", "checked")%> />   -
    - > - <%- if self.widget == "checkbox" then -%> - <%:create:%> - <%- else -%> - <%:unspecified -or- create:%> - <%- end -%>  - onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> -
    -
  • - <% elseif self.widget ~= "checkbox" and self.unspecified then %> -
  • - " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= - attr("type", self.widget or "radio") .. - attr("id", cbid .. "_uns") .. - attr("name", cbid) .. - attr("value", "") .. - ifattr(not value or #value == 0, "checked", "checked") - %> />   -
    - ><%:unspecified%> -
    -
  • - <% end %> -
- -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/nsection.htm b/modules/base/luasrc/luasrc/view/cbi/nsection.htm deleted file mode 100644 index 95e7658822..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/nsection.htm +++ /dev/null @@ -1,31 +0,0 @@ -<% if self:cfgvalue(self.section) then section = self.section %> -
- <% if self.title and #self.title > 0 then -%> - <%=self.title%> - <%- end %> - <% if self.description and #self.description > 0 then -%> -
<%=self.description%>
- <%- end %> - <% if self.addremove then -%> -
- -
- <%- end %> - <%+cbi/tabmenu%> -
- <%+cbi/ucisection%> -
-
-
-<% elseif self.addremove then %> - <% if self.template_addremove then include(self.template_addremove) else -%> -
- <% if self.title and #self.title > 0 then -%> - <%=self.title%> - <%- end %> -
<%=self.description%>
- -
- <%- end %> -<% end %> - diff --git a/modules/base/luasrc/luasrc/view/cbi/nullsection.htm b/modules/base/luasrc/luasrc/view/cbi/nullsection.htm deleted file mode 100644 index bd48950958..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/nullsection.htm +++ /dev/null @@ -1,38 +0,0 @@ -
- <% if self.title and #self.title > 0 then -%> - <%=self.title%> - <%- end %> - <% if self.description and #self.description > 0 then -%> -
<%=self.description%>
- <%- end %> -
-
- <% self:render_children(1, scope or {}) %> -
- <% if self.error and self.error[1] then -%> -
-
    <% for _, e in ipairs(self.error[1]) do -%> -
  • - <%- if e == "invalid" then -%> - <%:One or more fields contain invalid values!%> - <%- elseif e == "missing" then -%> - <%:One or more required fields have no value!%> - <%- else -%> - <%=pcdata(e)%> - <%- end -%> -
  • - <%- end %>
-
- <%- end %> -
-
-
-<%- - if type(self.hidden) == "table" then - for k, v in pairs(self.hidden) do --%> - -<%- - end - end -%> diff --git a/modules/base/luasrc/luasrc/view/cbi/simpleform.htm b/modules/base/luasrc/luasrc/view/cbi/simpleform.htm deleted file mode 100644 index 5216cd50f1..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/simpleform.htm +++ /dev/null @@ -1,57 +0,0 @@ -<% if not self.embedded then %> - -
- - -
-<% end %> -
- <% if self.title and #self.title > 0 then %>

<%=self.title%>

<% end %> - <% if self.description and #self.description > 0 then %>
<%=self.description%>
<% end %> - <% self:render_children() %> -
-
-<%- if self.message then %> -
<%=self.message%>
-<%- end %> -<%- if self.errmessage then %> -
<%=self.errmessage%>
-<%- end %> -<% if not self.embedded then %> -
-<%- - if type(self.hidden) == "table" then - for k, v in pairs(self.hidden) do --%> - -<%- - end - end -%> -<% if redirect then %> -
- -
-<% end %> -<%- if self.flow and self.flow.skip then %> - -<% end %> -<%- if self.submit ~= false then %> - -<% end %> -<%- if self.reset ~= false then %> - -<% end %> -<%- if self.cancel ~= false and self.on_cancel then %> - -<% end %> - -
- -<% end %> diff --git a/modules/base/luasrc/luasrc/view/cbi/tabcontainer.htm b/modules/base/luasrc/luasrc/view/cbi/tabcontainer.htm deleted file mode 100644 index 38c435d6a1..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/tabcontainer.htm +++ /dev/null @@ -1,7 +0,0 @@ -<% for tab, data in pairs(self.tabs) do %> -
style="display:none"<% end %>> - <% if data.description then %>
<%=data.description%>
<% end %> - <% self:render_tab(tab, section, scope or {}) %> -
- -<% end %> diff --git a/modules/base/luasrc/luasrc/view/cbi/tabmenu.htm b/modules/base/luasrc/luasrc/view/cbi/tabmenu.htm deleted file mode 100644 index b96ac9ce4b..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/tabmenu.htm +++ /dev/null @@ -1,13 +0,0 @@ -<%- if self.tabs then %> -
    - <%- self.selected_tab = luci.http.formvalue("tab." .. self.config .. "." .. section) %> - <%- for _, tab in ipairs(self.tab_names) do if #self.tabs[tab].childs > 0 then %> - - <%- if not self.selected_tab then self.selected_tab = tab end %> -
  • - <%=self.tabs[tab].title%> - <% if tab == self.selected_tab then %><% end %> -
  • - <% end end -%> -
-<% end -%> diff --git a/modules/base/luasrc/luasrc/view/cbi/tblsection.htm b/modules/base/luasrc/luasrc/view/cbi/tblsection.htm deleted file mode 100644 index d928791167..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/tblsection.htm +++ /dev/null @@ -1,146 +0,0 @@ -<%- -local rowcnt = 1 -function rowstyle() - rowcnt = rowcnt + 1 - return (rowcnt % 2) + 1 -end - -function width(o) - if o.width then - if type(o.width) == 'number' then - return ' style="width:%dpx"' % o.width - end - return ' style="width:%s"' % o.width - end - return '' -end --%> - - -
- <% if self.title and #self.title > 0 then -%> - <%=self.title%> - <%- end %> - <%- if self.sortable then -%> - - <%- end -%> -
<%=self.description%>
-
- <%- local count = 0 -%> - - - <%- if not self.anonymous then -%> - <%- if self.sectionhead then -%> - - <%- else -%> - - <%- end -%> - <%- end -%> - <%- for i, k in pairs(self.children) do if not k.optional then -%> - - <%- count = count + 1; end; end; if self.sortable then -%> - - <%- end; if self.extedit or self.addremove then -%> - - <%- count = count + 1; end -%> - - - <%- if not self.anonymous then -%> - <%- if self.sectiondesc then -%> - - <%- else -%> - - <%- end -%> - <%- end -%> - <%- for i, k in pairs(self.children) do if not k.optional then -%> - - <%- end; end; if self.sortable then -%> - - <%- end; if self.extedit or self.addremove then -%> - - <%- end -%> - - <%- local isempty = true - for i, k in ipairs(self:cfgsections()) do - section = k - isempty = false - scope = { valueheader = "cbi/cell_valueheader", valuefooter = "cbi/cell_valuefooter" } - -%> - - <% if not self.anonymous then -%> - - <%- end %> - - - <%- - for k, node in ipairs(self.children) do - if not node.optional then - node:render(section, scope or {}) - end - end - -%> - - <%- if self.sortable then -%> - - <%- end -%> - - <%- if self.extedit or self.addremove then -%> - - <%- end -%> - - <%- end -%> - - <%- if isempty then -%> - - - - <%- end -%> -
<%=self.sectionhead%> > - <%- if k.titleref then -%><%- end -%> - <%-=k.title-%> - <%- if k.titleref then -%><%- end -%> - <%:Sort%> 
<%=self.sectiondesc%>><%=k.description%>

<%=(type(self.sectiontitle) == "function") and self:sectiontitle(section) or k%>

- - - - <%- if self.extedit then -%> - onclick="location.href='<%=self.extedit:format(section)%>'" - <%- elseif type(self.extedit) == "function" then - %> onclick="location.href='<%=self:extedit(section)%>'" - <%- end - %> alt="<%:Edit%>" title="<%:Edit%>" /> - <%- end; if self.addremove then %> - - <%- end -%> -

<%:This section contains no values yet%>
- - <% if self.error then %> -
-
    <% for _, c in pairs(self.error) do for _, e in ipairs(c) do -%> -
  • <%=pcdata(e):gsub("\n","
    ")%>
  • - <%- end end %>
-
- <% end %> - - <%- if self.addremove then -%> - <% if self.template_addremove then include(self.template_addremove) else -%> -
- <% if self.anonymous then %> - - <% else %> - <% if self.invalid_cts then -%>
<% end %> - - - - <% if self.invalid_cts then -%> -
<%:Invalid%>
- <%- end %> - <% end %> -
- <%- end %> - <%- end -%> -
-
- diff --git a/modules/base/luasrc/luasrc/view/cbi/tsection.htm b/modules/base/luasrc/luasrc/view/cbi/tsection.htm deleted file mode 100644 index 087548bf28..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/tsection.htm +++ /dev/null @@ -1,48 +0,0 @@ -
- <% if self.title and #self.title > 0 then -%> - <%=self.title%> - <%- end %> -
<%=self.description%>
- <% local isempty = true for i, k in ipairs(self:cfgsections()) do -%> - <% if self.addremove then -%> -
- -
- <%- end %> - - <%- section = k; isempty = false -%> - - <% if not self.anonymous then -%> -

<%=section:upper()%>

- <%- end %> - - <%+cbi/tabmenu%> - -
- <%+cbi/ucisection%> -
-
- <%- end %> - - <% if isempty then -%> - <%:This section contains no values yet%>

- <%- end %> - - <% if self.addremove then -%> - <% if self.template_addremove then include(self.template_addremove) else -%> -
- <% if self.anonymous then -%> - - <%- else -%> - <% if self.invalid_cts then -%>
<% end %> - - - - <% if self.invalid_cts then -%> -
<%:Invalid%>
- <%- end %> - <%- end %> -
- <%- end %> - <%- end %> -
diff --git a/modules/base/luasrc/luasrc/view/cbi/tvalue.htm b/modules/base/luasrc/luasrc/view/cbi/tvalue.htm deleted file mode 100644 index fcf7a6c94c..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/tvalue.htm +++ /dev/null @@ -1,5 +0,0 @@ -<%+cbi/valueheader%> - -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/ucisection.htm b/modules/base/luasrc/luasrc/view/cbi/ucisection.htm deleted file mode 100644 index 3b69f12f2e..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/ucisection.htm +++ /dev/null @@ -1,75 +0,0 @@ -<%- - if type(self.hidden) == "table" then - for k, v in pairs(self.hidden) do --%> - -<%- - end - end -%> - -<% if self.tabs then %> - <%+cbi/tabcontainer%> -<% else %> - <% self:render_children(section, scope or {}) %> -<% end %> - -<% if self.error and self.error[section] then -%> -
-
    <% for _, e in ipairs(self.error[section]) do -%> -
  • - <%- if e == "invalid" then -%> - <%:One or more fields contain invalid values!%> - <%- elseif e == "missing" then -%> - <%:One or more required fields have no value!%> - <%- else -%> - <%=pcdata(e)%> - <%- end -%> -
  • - <%- end %>
-
-<%- end %> - -<% if self.optionals[section] and #self.optionals[section] > 0 or self.dynamic then %> -
- <% if self.dynamic then %> - - <% if self.optionals[section] and #self.optionals[section] > 0 then %> - - <% end %> - <% else %> - - - <% end %> - -
-<% end %> diff --git a/modules/base/luasrc/luasrc/view/cbi/upload.htm b/modules/base/luasrc/luasrc/view/cbi/upload.htm deleted file mode 100644 index 7770934111..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/upload.htm +++ /dev/null @@ -1,14 +0,0 @@ -<% - local t = require("luci.tools.webadmin") - local v = self:cfgvalue(section) - local s = v and nixio.fs.stat(v) --%> -<%+cbi/valueheader%> - <% if s then %> - <%:Uploaded File%> (<%=t.byte_format(s.size)%>) - /> - " alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" /> - <% else %> - /> - <% end %> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/value.htm b/modules/base/luasrc/luasrc/view/cbi/value.htm deleted file mode 100644 index d1a7bea5c6..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/value.htm +++ /dev/null @@ -1,35 +0,0 @@ -<%+cbi/valueheader%> - /> - <% if self.password then %><% end %> - <% if #self.keylist > 0 or self.datatype then -%> - - <% end -%> -<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/luasrc/view/cbi/valuefooter.htm b/modules/base/luasrc/luasrc/view/cbi/valuefooter.htm deleted file mode 100644 index 805312e451..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/valuefooter.htm +++ /dev/null @@ -1 +0,0 @@ -<% include( valuefooter or "cbi/full_valuefooter" ) %> diff --git a/modules/base/luasrc/luasrc/view/cbi/valueheader.htm b/modules/base/luasrc/luasrc/view/cbi/valueheader.htm deleted file mode 100644 index 761a54aed0..0000000000 --- a/modules/base/luasrc/luasrc/view/cbi/valueheader.htm +++ /dev/null @@ -1 +0,0 @@ -<% include( valueheader or "cbi/full_valueheader" ) %> diff --git a/modules/base/luasrc/sauth.lua b/modules/base/luasrc/sauth.lua new file mode 100644 index 0000000000..32f172dcda --- /dev/null +++ b/modules/base/luasrc/sauth.lua @@ -0,0 +1,127 @@ +--[[ + +Session authentication +(c) 2008 Steven Barth + +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$ + +]]-- + +--- LuCI session library. +module("luci.sauth", package.seeall) +require("luci.util") +require("luci.sys") +require("luci.config") +local nixio = require "nixio", require "nixio.util" +local fs = require "nixio.fs" + + +luci.config.sauth = luci.config.sauth or {} +sessionpath = luci.config.sauth.sessionpath +sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60 + +--- Prepare session storage by creating the session directory. +function prepare() + fs.mkdir(sessionpath, 700) + if not sane() then + error("Security Exception: Session path is not sane!") + end +end + +local function _read(id) + local blob = fs.readfile(sessionpath .. "/" .. id) + return blob +end + +local function _write(id, data) + local f = nixio.open(sessionpath .. "/" .. id, "w", 600) + f:writeall(data) + f:close() +end + +local function _checkid(id) + return not not (id and #id == 32 and id:match("^[a-fA-F0-9]+$")) +end + +--- Write session data to a session file. +-- @param id Session identifier +-- @param data Session data table +function write(id, data) + if not sane() then + prepare() + end + + assert(_checkid(id), "Security Exception: Session ID is invalid!") + assert(type(data) == "table", "Security Exception: Session data invalid!") + + data.atime = luci.sys.uptime() + + _write(id, luci.util.get_bytecode(data)) +end + +--- Read a session and return its content. +-- @param id Session identifier +-- @return Session data table or nil if the given id is not found +function read(id) + if not id or #id == 0 then + return nil + end + + assert(_checkid(id), "Security Exception: Session ID is invalid!") + + if not sane(sessionpath .. "/" .. id) then + return nil + end + + local blob = _read(id) + local func = loadstring(blob) + setfenv(func, {}) + + local sess = func() + assert(type(sess) == "table", "Session data invalid!") + + if sess.atime and sess.atime + sessiontime < luci.sys.uptime() then + kill(id) + return nil + end + + -- refresh atime in session + write(id, sess) + + return sess +end + +--- Check whether Session environment is sane. +-- @return Boolean status +function sane(file) + return luci.sys.process.info("uid") + == fs.stat(file or sessionpath, "uid") + and fs.stat(file or sessionpath, "modestr") + == (file and "rw-------" or "rwx------") +end + +--- Kills a session +-- @param id Session identifier +function kill(id) + assert(_checkid(id), "Security Exception: Session ID is invalid!") + fs.unlink(sessionpath .. "/" .. id) +end + +--- Remove all expired session data files +function reap() + if sane() then + local id + for id in nixio.fs.dir(sessionpath) do + if _checkid(id) then + -- reading the session will kill it if it is expired + read(id) + end + end + end +end diff --git a/modules/base/luasrc/template.lua b/modules/base/luasrc/template.lua new file mode 100644 index 0000000000..72127d1df1 --- /dev/null +++ b/modules/base/luasrc/template.lua @@ -0,0 +1,107 @@ +--[[ +LuCI - Template Parser + +Description: +A template parser supporting includes, translations, Lua code blocks +and more. It can be used either as a compiler or as an interpreter. + +FileId: $Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +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 + + +-- 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) + + self.template = self.cache[name] + self.name = name + + -- 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 = viewdir .. "/" .. name .. ".htm" + + self.template, _, err = tparser.parse(sourcefile) + + -- 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")) + else + 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 diff --git a/modules/base/luasrc/view/cbi/apply_xhr.htm b/modules/base/luasrc/view/cbi/apply_xhr.htm new file mode 100644 index 0000000000..1814c9393b --- /dev/null +++ b/modules/base/luasrc/view/cbi/apply_xhr.htm @@ -0,0 +1,43 @@ +<% export("cbi_apply_xhr", function(id, configs, redirect) -%> +
+ <%:Applying changes%> + + + <%:Loading%> + <%:Waiting for changes to be applied...%> +
+<%- end) %> diff --git a/modules/base/luasrc/view/cbi/browser.htm b/modules/base/luasrc/view/cbi/browser.htm new file mode 100644 index 0000000000..e4a4077d55 --- /dev/null +++ b/modules/base/luasrc/view/cbi/browser.htm @@ -0,0 +1,7 @@ +<% local v = self:cfgvalue(section) -%> +<%+cbi/valueheader%> + /> + +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/button.htm b/modules/base/luasrc/view/cbi/button.htm new file mode 100644 index 0000000000..30f8ddfda5 --- /dev/null +++ b/modules/base/luasrc/view/cbi/button.htm @@ -0,0 +1,7 @@ +<%+cbi/valueheader%> + <% if self:cfgvalue(section) ~= false then %> + " type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> /> + <% else %> + - + <% end %> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/cell_valuefooter.htm b/modules/base/luasrc/view/cbi/cell_valuefooter.htm new file mode 100644 index 0000000000..220ebd42ba --- /dev/null +++ b/modules/base/luasrc/view/cbi/cell_valuefooter.htm @@ -0,0 +1,20 @@ +
+
">
+ + +<% if #self.deps > 0 then -%> + +<%- end %> diff --git a/modules/base/luasrc/view/cbi/cell_valueheader.htm b/modules/base/luasrc/view/cbi/cell_valueheader.htm new file mode 100644 index 0000000000..9e2e145ddb --- /dev/null +++ b/modules/base/luasrc/view/cbi/cell_valueheader.htm @@ -0,0 +1,2 @@ + +
"> diff --git a/modules/base/luasrc/view/cbi/compound.htm b/modules/base/luasrc/view/cbi/compound.htm new file mode 100644 index 0000000000..12d02bb1d8 --- /dev/null +++ b/modules/base/luasrc/view/cbi/compound.htm @@ -0,0 +1 @@ +<%- self:render_children() %> diff --git a/modules/base/luasrc/view/cbi/delegator.htm b/modules/base/luasrc/view/cbi/delegator.htm new file mode 100644 index 0000000000..4fd19265d8 --- /dev/null +++ b/modules/base/luasrc/view/cbi/delegator.htm @@ -0,0 +1,24 @@ +<%- self.active:render() %> +
+ +<% for _, x in ipairs(self.chain) do %> + +<% end %> +<% if not self.disallow_pageactions then %> +<% if self.allow_finish and not self:get_next(self.current) then %> + +<% elseif self:get_next(self.current) then %> + +<% end %> +<% if self.allow_cancel then %> + +<% end %> +<% if self.allow_reset then %> + +<% end %> +<% if self.allow_back and self:get_prev(self.current) then %> + +<% end %> +<% end %> + +
diff --git a/modules/base/luasrc/view/cbi/dvalue.htm b/modules/base/luasrc/view/cbi/dvalue.htm new file mode 100644 index 0000000000..78e6f323d7 --- /dev/null +++ b/modules/base/luasrc/view/cbi/dvalue.htm @@ -0,0 +1,13 @@ +<%+cbi/valueheader%> +<% if self.href then %><% end -%> + <% + local val = self:cfgvalue(section) or self.default or "" + if not self.rawhtml then + write(pcdata(val)) + else + write(val) + end + %> +<%- if self.href then %><%end%> +" /> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/dynlist.htm b/modules/base/luasrc/view/cbi/dynlist.htm new file mode 100644 index 0000000000..fd626a4ecf --- /dev/null +++ b/modules/base/luasrc/view/cbi/dynlist.htm @@ -0,0 +1,26 @@ +<%+cbi/valueheader%> +
+<% + local vals = self:cfgvalue(section) or {} + for i=1, #vals + 1 do + local val = vals[i] + if (val and #val > 0) or (i == 1) then +%> + />
+<% end end %> +
+ +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/filebrowser.htm b/modules/base/luasrc/view/cbi/filebrowser.htm new file mode 100644 index 0000000000..a79beebba7 --- /dev/null +++ b/modules/base/luasrc/view/cbi/filebrowser.htm @@ -0,0 +1,108 @@ + + + + + Filebrowser - LuCI + + + + + + <% + require("nixio.fs") + require("nixio.util") + require("luci.http") + require("luci.dispatcher") + + local field = luci.http.formvalue('field') + local request = luci.dispatcher.context.args + local path = { '' } + + for i = 1, #request do + if request[i] ~= '..' and #request[i] > 0 then + path[#path+1] = request[i] + end + end + + local filepath = table.concat( path, '/' ) + local filestat = nixio.fs.stat( filepath ) + local baseurl = luci.dispatcher.build_url('admin', 'filebrowser') + + if filestat and filestat.type == "reg" then + table.remove( path, #path ) + filepath = table.concat( path, '/' ) .. '/' + elseif not ( filestat and filestat.type == "dir" ) then + path = { '' } + filepath = '/' + else + filepath = filepath .. '/' + end + + local entries = nixio.util.consume((nixio.fs.dir(filepath))) + -%> +
+ Location: + <% for i, dir in ipairs(path) do %> + <% if i == 1 then %> + (root) + <% elseif next(path, i) then %> + <% baseurl = baseurl .. '/' .. dir %> + / <%=dir%> + <% else %> + <% baseurl = baseurl .. '/' .. dir %> + / <%=dir%> + <% end %> + <% end %> +
+ +
+ +
+
    + <% for _, e in luci.util.vspairs(entries) do + local stat = nixio.fs.stat(filepath..e) + if stat and stat.type == 'dir' then + -%> +
  • + <%:Directory%> + <%=e%>/ +
  • + <% end end -%> + + <% for _, e in luci.util.vspairs(entries) do + local stat = nixio.fs.stat(filepath..e) + if stat and stat.type ~= 'dir' then + -%> +
  • + <%:File%> + <%=e%> +
  • + <% end end -%> +
+
+ + diff --git a/modules/base/luasrc/view/cbi/firewall_zoneforwards.htm b/modules/base/luasrc/view/cbi/firewall_zoneforwards.htm new file mode 100644 index 0000000000..2a433b5696 --- /dev/null +++ b/modules/base/luasrc/view/cbi/firewall_zoneforwards.htm @@ -0,0 +1,59 @@ +<%+cbi/valueheader%> + +<%- + local utl = require "luci.util" + local fwm = require "luci.model.firewall".init() + local nwm = require "luci.model.network".init() + + local zone, fwd, fz + local value = self:formvalue(section) + if not value or value == "-" then + value = self:cfgvalue(section) or self.default + end + + local def = fwm:get_defaults() + local zone = fwm:get_zone(value) + local empty = true +-%> + +<% if zone then %> +
+ +  ⇒  + <% for _, fwd in ipairs(zone:get_forwardings_by("src")) do + fz = fwd:dest_zone() + empty = false %> +   + <% end %> + <% if empty then %> + + <% end %> +
+<% end %> + +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/firewall_zonelist.htm b/modules/base/luasrc/view/cbi/firewall_zonelist.htm new file mode 100644 index 0000000000..7973437f40 --- /dev/null +++ b/modules/base/luasrc/view/cbi/firewall_zonelist.htm @@ -0,0 +1,89 @@ +<%+cbi/valueheader%> + +<%- + local utl = require "luci.util" + local fwm = require "luci.model.firewall".init() + local nwm = require "luci.model.network".init() + + local zone, net, iface + local zones = fwm:get_zones() + local value = self:formvalue(section) + if not value or value == "-" then + value = self:cfgvalue(section) or self.default + end + + local selected = false + local checked = { } + + for value in utl.imatch(value) do + checked[value] = true + end + + if not next(checked) then + checked[""] = true + end +-%> + +
    + <% if self.allowlocal then %> +
  • + />   + style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> + <%:Device%> + <% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %> + +
  • + <% end %> + <% if self.allowany then %> +
  • + />   + style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge"> + <%:Any zone%> + <% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %> + +
  • + <% end %> + <% + for _, zone in utl.spairs(zones, function(a,b) return (zones[a]:name() < zones[b]:name()) end) do + if zone:name() ~= self.exclude then + selected = selected or (value == zone:name()) + %> +
  • + />   + style="background-color:<%=zone:get_color()%>" class="zonebadge"> + <%=zone:name()%>: + <% + local zempty = true + for _, net in ipairs(zone:get_networks()) do + net = nwm:get_network(net) + if net then + zempty = false + %> + <%=net:name()%>: + <% + local nempty = true + for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do + nempty = false + %> + style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> + <% end %> + <% if nempty then %><%:(empty)%><% end %> + + <% end end %> + <% if zempty then %><%:(empty)%><% end %> + +
  • + <% end end %> + + <% if self.widget ~= "checkbox" and not self.nocreate then %> +
  • + />   +
    + <%:unspecified -or- create:%>  + onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> +
    +
  • + <% end %> +
+ +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/footer.htm b/modules/base/luasrc/view/cbi/footer.htm new file mode 100644 index 0000000000..2c34028e58 --- /dev/null +++ b/modules/base/luasrc/view/cbi/footer.htm @@ -0,0 +1,26 @@ + <%- if pageaction then -%> +
+ <% if redirect then %> +
+ +
+ <% end %> + + <% if flow.skip then %> + + <% end %> + <% if not autoapply and not flow.hideapplybtn then %> + + <% end %> + <% if not flow.hidesavebtn then %> + + <% end %> + <% if not flow.hideresetbtn then %> + + <% end %> + + +
+ <%- end -%> + +<%+footer%> diff --git a/modules/base/luasrc/view/cbi/full_valuefooter.htm b/modules/base/luasrc/view/cbi/full_valuefooter.htm new file mode 100644 index 0000000000..4876fbcc99 --- /dev/null +++ b/modules/base/luasrc/view/cbi/full_valuefooter.htm @@ -0,0 +1,59 @@ + <% if self.description and #self.description > 0 then -%> + <% if not luci.util.instanceof(self, luci.cbi.DynamicList) and (not luci.util.instanceof(self, luci.cbi.Flag) or self.orientation == "horizontal") then -%> +
+ <%- end %> +
+ <%:help%> + <%=self.description%> +
+ <%- end %> + <%- if self.title and #self.title > 0 then -%> +
+ <%- end -%> +
+ + +<% if #self.deps > 0 or #self.subdeps > 0 then -%> + +<%- end %> diff --git a/modules/base/luasrc/view/cbi/full_valueheader.htm b/modules/base/luasrc/view/cbi/full_valueheader.htm new file mode 100644 index 0000000000..aaf085473a --- /dev/null +++ b/modules/base/luasrc/view/cbi/full_valueheader.htm @@ -0,0 +1,9 @@ +
"> + <%- if self.title and #self.title > 0 then -%> + +
+ <%- end -%> diff --git a/modules/base/luasrc/view/cbi/fvalue.htm b/modules/base/luasrc/view/cbi/fvalue.htm new file mode 100644 index 0000000000..a1e0808e8d --- /dev/null +++ b/modules/base/luasrc/view/cbi/fvalue.htm @@ -0,0 +1,9 @@ +<%+cbi/valueheader%> + /> + /> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/header.htm b/modules/base/luasrc/view/cbi/header.htm new file mode 100644 index 0000000000..2bddaba61a --- /dev/null +++ b/modules/base/luasrc/view/cbi/header.htm @@ -0,0 +1,7 @@ +<%+header%> +
+
+ + + +
diff --git a/modules/base/luasrc/view/cbi/lvalue.htm b/modules/base/luasrc/view/cbi/lvalue.htm new file mode 100644 index 0000000000..8cc086db42 --- /dev/null +++ b/modules/base/luasrc/view/cbi/lvalue.htm @@ -0,0 +1,18 @@ +<%+cbi/valueheader%> +<% if self.widget == "select" then %> + +<% elseif self.widget == "radio" then + local c = 0 + for i, key in pairs(self.keylist) do + c = c + 1 +%> + /> + ><%=self.vallist[i]%> +<% if c == self.size then c = 0 %><% if self.orientation == "horizontal" then %> <% else %>
<% end %> +<% end end %> +<% end %> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/map.htm b/modules/base/luasrc/view/cbi/map.htm new file mode 100644 index 0000000000..053220d185 --- /dev/null +++ b/modules/base/luasrc/view/cbi/map.htm @@ -0,0 +1,13 @@ +<%- if firstmap and messages then local msg; for _, msg in ipairs(messages) do -%> +
<%=pcdata(msg)%>
+<%- end end -%> + +<%-+cbi/apply_xhr-%> + +
+ <% if self.title and #self.title > 0 then %>

<%=self.title%>

<% end %> + <% if self.description and #self.description > 0 then %>
<%=self.description%>
<% end %> + <%- if firstmap and applymap then cbi_apply_xhr(self.config, parsechain, redirect) end -%> + <%- self:render_children() %> +
+
diff --git a/modules/base/luasrc/view/cbi/mvalue.htm b/modules/base/luasrc/view/cbi/mvalue.htm new file mode 100644 index 0000000000..6a0b3881d0 --- /dev/null +++ b/modules/base/luasrc/view/cbi/mvalue.htm @@ -0,0 +1,19 @@ +<% local v = self:valuelist(section) or {} -%> +<%+cbi/valueheader%> +<% if self.widget == "select" then %> + +<% elseif self.widget == "checkbox" then + local c = 0; + for i, key in pairs(self.keylist) do + c = c + 1 +%> + /> + ><%=self.vallist[i]%>
+<% if c == self.size then c = 0 %>
+<% end end %> +<% end %> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/network_ifacelist.htm b/modules/base/luasrc/view/cbi/network_ifacelist.htm new file mode 100644 index 0000000000..643d849a50 --- /dev/null +++ b/modules/base/luasrc/view/cbi/network_ifacelist.htm @@ -0,0 +1,81 @@ +<%+cbi/valueheader%> + +<%- + local utl = require "luci.util" + local net = require "luci.model.network".init() + local cbeid = luci.cbi.FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option + + local iface + local ifaces = net:get_interfaces() + local value + + if self.map:formvalue(cbeid) == "1" then + value = self:formvalue(section) or self.default or "" + else + value = self:cfgvalue(section) or self.default + end + + local checked = { } + + if value then + for value in utl.imatch(value) do + checked[value] = true + end + else + local n = self.network and net:get_network(self.network) + if n then + local i + for _, i in ipairs(n:get_interfaces() or { n:get_interface() }) do + checked[i:name()] = true + end + end + end +-%> + + +
    + <% for _, iface in ipairs(ifaces) do + local link = iface:adminlink() + if (not self.nobridges or not iface:is_bridge()) and + (not self.noinactive or iface:is_up()) and + iface:name() ~= self.exclude + then %> +
  • + " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= + attr("type", self.widget or "radio") .. + attr("id", cbid .. "." .. iface:name()) .. + attr("name", cbid) .. attr("value", iface:name()) .. + ifattr(checked[iface:name()], "checked", "checked") + %> />   + > + <% if link then -%><% end -%> + style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> + <% if link then -%><% end -%> + <%=pcdata(iface:get_i18n())%> + <% local ns = iface:get_networks(); if #ns > 0 then %>( + <%- local i, n; for i, n in ipairs(ns) do -%> + <%-= (i>1) and ', ' -%> + <%=n:name()%> + <%- end -%> + )<% end %> + +
  • + <% end end %> + <% if not self.nocreate then %> +
  • + " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= + attr("type", self.widget or "radio") .. + attr("id", cbid .. "_custom") .. + attr("name", cbid) .. + attr("value", " ") + %> />   + > + + <%:Custom Interface%>: + + +
  • + <% end %> +
+ +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/network_netinfo.htm b/modules/base/luasrc/view/cbi/network_netinfo.htm new file mode 100644 index 0000000000..4fd84112a4 --- /dev/null +++ b/modules/base/luasrc/view/cbi/network_netinfo.htm @@ -0,0 +1,27 @@ +<%+cbi/valueheader%> + +<%- + local value = self:formvalue(section) + if not value or value == "-" then + value = self:cfgvalue(section) or self.default + end + + local nwm = require "luci.model.network".init() + local net = nwm:get_network(value) +-%> + +<% if net then %> +<%=net:name()%>: + <% + local empty = true + for _, iface in ipairs(net:get_interfaces() or { net:get_interface() }) do + if not iface:is_bridge() then + empty = false + %> + style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> + <% end end %> + <% if empty then %><%:(no interfaces attached)%><% end %> + +<% end %> + +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/network_netlist.htm b/modules/base/luasrc/view/cbi/network_netlist.htm new file mode 100644 index 0000000000..7e23d149a8 --- /dev/null +++ b/modules/base/luasrc/view/cbi/network_netlist.htm @@ -0,0 +1,81 @@ +<%+cbi/valueheader%> + +<%- + local utl = require "luci.util" + local nwm = require "luci.model.network".init() + + local net, iface + local networks = nwm:get_networks() + local value = self:formvalue(section) + + self.cast = nil + + if not value or value == "-" then + value = self:cfgvalue(section) or self.default + end + + local checked = { } + for value in utl.imatch(value) do + checked[value] = true + end +-%> + +
    + <% for _, net in ipairs(networks) do + if (net:name() ~= "loopback") and + (net:name() ~= self.exclude) and + (not self.novirtual or not net:is_virtual()) + then %> +
  • + " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= + attr("type", self.widget or "radio") .. + attr("id", cbid .. "." .. net:name()) .. + attr("name", cbid) .. attr("value", net:name()) .. + ifattr(checked[net:name()], "checked", "checked") + %> />   + > + <%=net:name()%>: + <% + local empty = true + for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do + if not iface:is_bridge() then + empty = false + %> + style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" /> + <% end end %> + <% if empty then %><%:(no interfaces attached)%><% end %> + + +
  • + <% end end %> + + <% if not self.nocreate then %> +
  • + " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not value and self.widget ~= "checkbox", "checked", "checked")%> />   +
    + > + <%- if self.widget == "checkbox" then -%> + <%:create:%> + <%- else -%> + <%:unspecified -or- create:%> + <%- end -%>  + onfocus="document.getElementById('<%=cbid%>_new').checked=true" /> +
    +
  • + <% elseif self.widget ~= "checkbox" and self.unspecified then %> +
  • + " onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)"<%= + attr("type", self.widget or "radio") .. + attr("id", cbid .. "_uns") .. + attr("name", cbid) .. + attr("value", "") .. + ifattr(not value or #value == 0, "checked", "checked") + %> />   +
    + ><%:unspecified%> +
    +
  • + <% end %> +
+ +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/nsection.htm b/modules/base/luasrc/view/cbi/nsection.htm new file mode 100644 index 0000000000..95e7658822 --- /dev/null +++ b/modules/base/luasrc/view/cbi/nsection.htm @@ -0,0 +1,31 @@ +<% if self:cfgvalue(self.section) then section = self.section %> +
+ <% if self.title and #self.title > 0 then -%> + <%=self.title%> + <%- end %> + <% if self.description and #self.description > 0 then -%> +
<%=self.description%>
+ <%- end %> + <% if self.addremove then -%> +
+ +
+ <%- end %> + <%+cbi/tabmenu%> +
+ <%+cbi/ucisection%> +
+
+
+<% elseif self.addremove then %> + <% if self.template_addremove then include(self.template_addremove) else -%> +
+ <% if self.title and #self.title > 0 then -%> + <%=self.title%> + <%- end %> +
<%=self.description%>
+ +
+ <%- end %> +<% end %> + diff --git a/modules/base/luasrc/view/cbi/nullsection.htm b/modules/base/luasrc/view/cbi/nullsection.htm new file mode 100644 index 0000000000..bd48950958 --- /dev/null +++ b/modules/base/luasrc/view/cbi/nullsection.htm @@ -0,0 +1,38 @@ +
+ <% if self.title and #self.title > 0 then -%> + <%=self.title%> + <%- end %> + <% if self.description and #self.description > 0 then -%> +
<%=self.description%>
+ <%- end %> +
+
+ <% self:render_children(1, scope or {}) %> +
+ <% if self.error and self.error[1] then -%> +
+
    <% for _, e in ipairs(self.error[1]) do -%> +
  • + <%- if e == "invalid" then -%> + <%:One or more fields contain invalid values!%> + <%- elseif e == "missing" then -%> + <%:One or more required fields have no value!%> + <%- else -%> + <%=pcdata(e)%> + <%- end -%> +
  • + <%- end %>
+
+ <%- end %> +
+
+
+<%- + if type(self.hidden) == "table" then + for k, v in pairs(self.hidden) do +-%> + +<%- + end + end +%> diff --git a/modules/base/luasrc/view/cbi/simpleform.htm b/modules/base/luasrc/view/cbi/simpleform.htm new file mode 100644 index 0000000000..5216cd50f1 --- /dev/null +++ b/modules/base/luasrc/view/cbi/simpleform.htm @@ -0,0 +1,57 @@ +<% if not self.embedded then %> + +
+ + +
+<% end %> +
+ <% if self.title and #self.title > 0 then %>

<%=self.title%>

<% end %> + <% if self.description and #self.description > 0 then %>
<%=self.description%>
<% end %> + <% self:render_children() %> +
+
+<%- if self.message then %> +
<%=self.message%>
+<%- end %> +<%- if self.errmessage then %> +
<%=self.errmessage%>
+<%- end %> +<% if not self.embedded then %> +
+<%- + if type(self.hidden) == "table" then + for k, v in pairs(self.hidden) do +-%> + +<%- + end + end +%> +<% if redirect then %> +
+ +
+<% end %> +<%- if self.flow and self.flow.skip then %> + +<% end %> +<%- if self.submit ~= false then %> + +<% end %> +<%- if self.reset ~= false then %> + +<% end %> +<%- if self.cancel ~= false and self.on_cancel then %> + +<% end %> + +
+ +<% end %> diff --git a/modules/base/luasrc/view/cbi/tabcontainer.htm b/modules/base/luasrc/view/cbi/tabcontainer.htm new file mode 100644 index 0000000000..38c435d6a1 --- /dev/null +++ b/modules/base/luasrc/view/cbi/tabcontainer.htm @@ -0,0 +1,7 @@ +<% for tab, data in pairs(self.tabs) do %> +
style="display:none"<% end %>> + <% if data.description then %>
<%=data.description%>
<% end %> + <% self:render_tab(tab, section, scope or {}) %> +
+ +<% end %> diff --git a/modules/base/luasrc/view/cbi/tabmenu.htm b/modules/base/luasrc/view/cbi/tabmenu.htm new file mode 100644 index 0000000000..b96ac9ce4b --- /dev/null +++ b/modules/base/luasrc/view/cbi/tabmenu.htm @@ -0,0 +1,13 @@ +<%- if self.tabs then %> +
    + <%- self.selected_tab = luci.http.formvalue("tab." .. self.config .. "." .. section) %> + <%- for _, tab in ipairs(self.tab_names) do if #self.tabs[tab].childs > 0 then %> + + <%- if not self.selected_tab then self.selected_tab = tab end %> +
  • + <%=self.tabs[tab].title%> + <% if tab == self.selected_tab then %><% end %> +
  • + <% end end -%> +
+<% end -%> diff --git a/modules/base/luasrc/view/cbi/tblsection.htm b/modules/base/luasrc/view/cbi/tblsection.htm new file mode 100644 index 0000000000..d928791167 --- /dev/null +++ b/modules/base/luasrc/view/cbi/tblsection.htm @@ -0,0 +1,146 @@ +<%- +local rowcnt = 1 +function rowstyle() + rowcnt = rowcnt + 1 + return (rowcnt % 2) + 1 +end + +function width(o) + if o.width then + if type(o.width) == 'number' then + return ' style="width:%dpx"' % o.width + end + return ' style="width:%s"' % o.width + end + return '' +end +-%> + + +
+ <% if self.title and #self.title > 0 then -%> + <%=self.title%> + <%- end %> + <%- if self.sortable then -%> + + <%- end -%> +
<%=self.description%>
+
+ <%- local count = 0 -%> + + + <%- if not self.anonymous then -%> + <%- if self.sectionhead then -%> + + <%- else -%> + + <%- end -%> + <%- end -%> + <%- for i, k in pairs(self.children) do if not k.optional then -%> + + <%- count = count + 1; end; end; if self.sortable then -%> + + <%- end; if self.extedit or self.addremove then -%> + + <%- count = count + 1; end -%> + + + <%- if not self.anonymous then -%> + <%- if self.sectiondesc then -%> + + <%- else -%> + + <%- end -%> + <%- end -%> + <%- for i, k in pairs(self.children) do if not k.optional then -%> + + <%- end; end; if self.sortable then -%> + + <%- end; if self.extedit or self.addremove then -%> + + <%- end -%> + + <%- local isempty = true + for i, k in ipairs(self:cfgsections()) do + section = k + isempty = false + scope = { valueheader = "cbi/cell_valueheader", valuefooter = "cbi/cell_valuefooter" } + -%> + + <% if not self.anonymous then -%> + + <%- end %> + + + <%- + for k, node in ipairs(self.children) do + if not node.optional then + node:render(section, scope or {}) + end + end + -%> + + <%- if self.sortable then -%> + + <%- end -%> + + <%- if self.extedit or self.addremove then -%> + + <%- end -%> + + <%- end -%> + + <%- if isempty then -%> + + + + <%- end -%> +
<%=self.sectionhead%> > + <%- if k.titleref then -%><%- end -%> + <%-=k.title-%> + <%- if k.titleref then -%><%- end -%> + <%:Sort%> 
<%=self.sectiondesc%>><%=k.description%>

<%=(type(self.sectiontitle) == "function") and self:sectiontitle(section) or k%>

+ + + + <%- if self.extedit then -%> + onclick="location.href='<%=self.extedit:format(section)%>'" + <%- elseif type(self.extedit) == "function" then + %> onclick="location.href='<%=self:extedit(section)%>'" + <%- end + %> alt="<%:Edit%>" title="<%:Edit%>" /> + <%- end; if self.addremove then %> + + <%- end -%> +

<%:This section contains no values yet%>
+ + <% if self.error then %> +
+
    <% for _, c in pairs(self.error) do for _, e in ipairs(c) do -%> +
  • <%=pcdata(e):gsub("\n","
    ")%>
  • + <%- end end %>
+
+ <% end %> + + <%- if self.addremove then -%> + <% if self.template_addremove then include(self.template_addremove) else -%> +
+ <% if self.anonymous then %> + + <% else %> + <% if self.invalid_cts then -%>
<% end %> + + + + <% if self.invalid_cts then -%> +
<%:Invalid%>
+ <%- end %> + <% end %> +
+ <%- end %> + <%- end -%> +
+
+ diff --git a/modules/base/luasrc/view/cbi/tsection.htm b/modules/base/luasrc/view/cbi/tsection.htm new file mode 100644 index 0000000000..087548bf28 --- /dev/null +++ b/modules/base/luasrc/view/cbi/tsection.htm @@ -0,0 +1,48 @@ +
+ <% if self.title and #self.title > 0 then -%> + <%=self.title%> + <%- end %> +
<%=self.description%>
+ <% local isempty = true for i, k in ipairs(self:cfgsections()) do -%> + <% if self.addremove then -%> +
+ +
+ <%- end %> + + <%- section = k; isempty = false -%> + + <% if not self.anonymous then -%> +

<%=section:upper()%>

+ <%- end %> + + <%+cbi/tabmenu%> + +
+ <%+cbi/ucisection%> +
+
+ <%- end %> + + <% if isempty then -%> + <%:This section contains no values yet%>

+ <%- end %> + + <% if self.addremove then -%> + <% if self.template_addremove then include(self.template_addremove) else -%> +
+ <% if self.anonymous then -%> + + <%- else -%> + <% if self.invalid_cts then -%>
<% end %> + + + + <% if self.invalid_cts then -%> +
<%:Invalid%>
+ <%- end %> + <%- end %> +
+ <%- end %> + <%- end %> +
diff --git a/modules/base/luasrc/view/cbi/tvalue.htm b/modules/base/luasrc/view/cbi/tvalue.htm new file mode 100644 index 0000000000..fcf7a6c94c --- /dev/null +++ b/modules/base/luasrc/view/cbi/tvalue.htm @@ -0,0 +1,5 @@ +<%+cbi/valueheader%> + +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/ucisection.htm b/modules/base/luasrc/view/cbi/ucisection.htm new file mode 100644 index 0000000000..3b69f12f2e --- /dev/null +++ b/modules/base/luasrc/view/cbi/ucisection.htm @@ -0,0 +1,75 @@ +<%- + if type(self.hidden) == "table" then + for k, v in pairs(self.hidden) do +-%> + +<%- + end + end +%> + +<% if self.tabs then %> + <%+cbi/tabcontainer%> +<% else %> + <% self:render_children(section, scope or {}) %> +<% end %> + +<% if self.error and self.error[section] then -%> +
+
    <% for _, e in ipairs(self.error[section]) do -%> +
  • + <%- if e == "invalid" then -%> + <%:One or more fields contain invalid values!%> + <%- elseif e == "missing" then -%> + <%:One or more required fields have no value!%> + <%- else -%> + <%=pcdata(e)%> + <%- end -%> +
  • + <%- end %>
+
+<%- end %> + +<% if self.optionals[section] and #self.optionals[section] > 0 or self.dynamic then %> +
+ <% if self.dynamic then %> + + <% if self.optionals[section] and #self.optionals[section] > 0 then %> + + <% end %> + <% else %> + + + <% end %> + +
+<% end %> diff --git a/modules/base/luasrc/view/cbi/upload.htm b/modules/base/luasrc/view/cbi/upload.htm new file mode 100644 index 0000000000..7770934111 --- /dev/null +++ b/modules/base/luasrc/view/cbi/upload.htm @@ -0,0 +1,14 @@ +<% + local t = require("luci.tools.webadmin") + local v = self:cfgvalue(section) + local s = v and nixio.fs.stat(v) +-%> +<%+cbi/valueheader%> + <% if s then %> + <%:Uploaded File%> (<%=t.byte_format(s.size)%>) + /> + " alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" /> + <% else %> + /> + <% end %> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/value.htm b/modules/base/luasrc/view/cbi/value.htm new file mode 100644 index 0000000000..d1a7bea5c6 --- /dev/null +++ b/modules/base/luasrc/view/cbi/value.htm @@ -0,0 +1,35 @@ +<%+cbi/valueheader%> + /> + <% if self.password then %><% end %> + <% if #self.keylist > 0 or self.datatype then -%> + + <% end -%> +<%+cbi/valuefooter%> diff --git a/modules/base/luasrc/view/cbi/valuefooter.htm b/modules/base/luasrc/view/cbi/valuefooter.htm new file mode 100644 index 0000000000..805312e451 --- /dev/null +++ b/modules/base/luasrc/view/cbi/valuefooter.htm @@ -0,0 +1 @@ +<% include( valuefooter or "cbi/full_valuefooter" ) %> diff --git a/modules/base/luasrc/view/cbi/valueheader.htm b/modules/base/luasrc/view/cbi/valueheader.htm new file mode 100644 index 0000000000..761a54aed0 --- /dev/null +++ b/modules/base/luasrc/view/cbi/valueheader.htm @@ -0,0 +1 @@ +<% include( valueheader or "cbi/full_valueheader" ) %>