diff options
Diffstat (limited to 'libs')
49 files changed, 4056 insertions, 0 deletions
diff --git a/libs/cbi/Makefile b/libs/cbi/Makefile new file mode 100644 index 000000000..81a96f6a8 --- /dev/null +++ b/libs/cbi/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk
\ No newline at end of file diff --git a/libs/cbi/root/www/resources/cbi.js b/libs/cbi/root/www/resources/cbi.js new file mode 100644 index 000000000..a3a47aa45 --- /dev/null +++ b/libs/cbi/root/www/resources/cbi.js @@ -0,0 +1,42 @@ +var cbi_d = {}; + +function cbi_d_add(field, target, value) { + if (!cbi_d[target]) { + cbi_d[target] = {}; + } + if (!cbi_d[target][value]) { + cbi_d[target][value] = []; + } + cbi_d[target][value].push(field); +} + +function cbi_d_update(target) { + if (!cbi_d[target]) { + return; + } + + for (var x in cbi_d[target]) { + for (var i=0; i<cbi_d[target][x].length; i++) { + var y = document.getElementById(cbi_d[target][x][i]) + y.style.display = "none"; + } + } + + var t = document.getElementById(target); + if (t && t.value && cbi_d[target][t.value]) { + for (var i=0; i<cbi_d[target][t.value].length; i++) { + var y = document.getElementById(cbi_d[target][t.value][i]) + if (!y.value) { + y.style.display = "table-row"; + } else { + y.style.display = "block"; + } + } + } +} + +function cbi_d_init() { + for (var x in cbi_d) { + cbi_d_update(x); + } +}
\ No newline at end of file diff --git a/libs/cbi/src/cbi.lua b/libs/cbi/src/cbi.lua new file mode 100644 index 000000000..b7097b5d9 --- /dev/null +++ b/libs/cbi/src/cbi.lua @@ -0,0 +1,747 @@ +--[[ +LuCI - Configuration Bind Interface + +Description: +Offers an interface for binding confiugration values to certain +data types. Supports value and range validation and basic dependencies. + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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") +require("luci.util") +require("luci.http") +require("luci.model.uci") + +local class = luci.util.class +local instanceof = luci.util.instanceof + +-- Loads a CBI map from given file, creating an environment and returns it +function load(cbimap) + require("luci.fs") + require("luci.i18n") + require("luci.config") + require("luci.sys") + + local cbidir = luci.sys.libpath() .. "/model/cbi/" + local func, err = loadfile(cbidir..cbimap..".lua") + + if not func then + return nil + end + + luci.i18n.loadc("cbi") + + luci.util.resfenv(func) + luci.util.updfenv(func, luci.cbi) + luci.util.extfenv(func, "translate", luci.i18n.translate) + + local map = func() + + if not instanceof(map, Map) then + error("CBI map returns no valid map object!") + return nil + end + + return map +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 + +-- 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, ...) + for k, node in ipairs(self.children) do + node:render(...) + end +end + + +--[[ +A simple template element +]]-- +Template = class(Node) + +function Template.__init__(self, template) + Node.__init__(self) + self.template = template +end + + +--[[ +Map - A map describing a configuration file +]]-- +Map = class(Node) + +function Map.__init__(self, config, ...) + Node.__init__(self, ...) + self.config = config + self.template = "cbi/map" + self.uci = luci.model.uci.Session() + self.ucidata, self.uciorder = self.uci:sections(self.config) + if not self.ucidata or not self.uciorder then + error("Unable to read UCI data: " .. self.config) + end +end + +-- Use optimized UCI writing +function Map.parse(self, ...) + self.uci:t_load(self.config) + Node.parse(self, ...) + self.uci:t_save(self.config) +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) + local name = self.uci:t_add(self.config, sectiontype) + if name then + self.ucidata[name] = {} + self.ucidata[name][".type"] = sectiontype + table.insert(self.uciorder, name) + end + return name +end + +-- UCI set +function Map.set(self, section, option, value) + local stat = self.uci:t_set(self.config, section, option, value) + if stat then + local val = self.uci:t_get(self.config, section, option) + if option then + self.ucidata[section][option] = val + else + if not self.ucidata[section] then + self.ucidata[section] = {} + end + self.ucidata[section][".type"] = val + table.insert(self.uciorder, section) + end + end + return stat +end + +-- UCI del +function Map.del(self, section, option) + local stat = self.uci:t_del(self.config, section, option) + if stat then + if option then + self.ucidata[section][option] = nil + else + self.ucidata[section] = nil + for i, k in ipairs(self.uciorder) do + if section == k then + table.remove(self.uciorder, i) + end + end + end + end + return stat +end + +-- UCI get (cached) +function Map.get(self, section, option) + if not section then + return self.ucidata, self.uciorder + elseif option and self.ucidata[section] then + return self.ucidata[section][option] + else + return self.ucidata[section] + end +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.optional = true + self.addremove = false + self.dynamic = false +end + +-- Appends a new option +function AbstractSection.option(self, class, ...) + if instanceof(class, AbstractValue) then + local obj = class(self.map, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractValue") + end +end + +-- Parse optional options +function AbstractSection.parse_optionals(self, section) + if not self.optional then + return + end + + self.optionals[section] = {} + + local field = luci.http.formvalue("cbi.opt."..self.config.."."..section) + for k,v in ipairs(self.children) do + if v.optional and not v:cfgvalue(section) then + if field == v.option then + field = nil + 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 = luci.http.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:add_dynamic(key, true) + end + end +end + +-- Returns the section's UCI table +function AbstractSection.cfgvalue(self, section) + return self.map:get(section) +end + +-- Removes the section +function AbstractSection.remove(self, section) + return self.map:del(section) +end + +-- Creates the section +function AbstractSection.create(self, section) + return self.map:set(section, nil, self.sectiontype) +end + + + +--[[ +NamedSection - A fixed configuration section defined by its name +]]-- +NamedSection = class(AbstractSection) + +function NamedSection.__init__(self, map, section, ...) + AbstractSection.__init__(self, map, ...) + self.template = "cbi/nsection" + + self.section = section + self.addremove = false +end + +function NamedSection.parse(self) + 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 luci.http.formvalue("cbi.rns."..path) and self:remove(s) then + return + end + else -- Create and apply default values + if luci.http.formvalue("cbi.cns."..path) and self:create(s) then + for k,v in pairs(self.children) do + v:write(s, v.default) + end + end + end + end + + if active then + AbstractSection.parse_dynamic(self, s) + if luci.http.formvalue("cbi.submit") then + Node.parse(self, s) + end + AbstractSection.parse_optionals(self, s) + 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, ...) + AbstractSection.__init__(self, ...) + self.template = "cbi/tsection" + self.deps = {} + self.excludes = {} + + self.anonymous = false +end + +-- Return all matching UCI sections for this TypedSection +function TypedSection.cfgsections(self) + local sections = {} + local map, order = self.map:get() + + for i, k in ipairs(order) do + if map[k][".type"] == self.sectiontype then + if self:checkscope(k) then + table.insert(sections, k) + end + end + end + + return sections +end + +-- Creates a new section of this type with the given name (or anonymous) +function TypedSection.create(self, name) + if name then + self.map:set(name, nil, self.sectiontype) + else + name = self.map:add(self.sectiontype) + end + + for k,v in pairs(self.children) do + if v.default then + self.map:set(name, v.option, v.default) + end + end +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 + +-- Excludes several sections by name +function TypedSection.exclude(self, field) + self.excludes[field] = true +end + +function TypedSection.parse(self) + if self.addremove then + -- Create + local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype + local name = luci.http.formvalue(crval) + if self.anonymous then + if name then + self:create() + 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:len() > 0 then + self:create(name) + end + end + end + + -- Remove + crval = "cbi.rts." .. self.config + name = luci.http.formvaluetable(crval) + for k,v in pairs(name) do + if self:cfgvalue(k) and self:checkscope(k) then + self:remove(k) + end + end + end + + for i, k in ipairs(self:cfgsections()) do + AbstractSection.parse_dynamic(self, k) + if luci.http.formvalue("cbi.submit") then + Node.parse(self, k) + end + AbstractSection.parse_optionals(self, k) + end +end + +-- Verifies scope of sections +function TypedSection.checkscope(self, section) + -- Check if we are not excluded + if self.excludes[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, option, ...) + Node.__init__(self, ...) + self.option = option + self.map = map + self.config = map.config + self.tag_invalid = {} + self.deps = {} + + self.rmempty = false + self.default = nil + self.size = nil + self.optional = false +end + +-- Add a dependencie to another section field +function AbstractValue.depends(self, field, value) + table.insert(self.deps, {field=field, value=value}) +end + +-- Return whether this object should be created +function AbstractValue.formcreated(self, section) + local key = "cbi.opt."..self.config.."."..section + return (luci.http.formvalue(key) == self.option) +end + +-- Returns the formvalue for this object +function AbstractValue.formvalue(self, section) + local key = "cbid."..self.map.config.."."..section.."."..self.option + return luci.http.formvalue(key) +end + +function AbstractValue.parse(self, section) + local fvalue = self:formvalue(section) + + if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI + fvalue = self:validate(fvalue) + if not fvalue then + self.tag_invalid[section] = true + end + if fvalue and not (fvalue == self:cfgvalue(section)) then + self:write(section, fvalue) + end + else -- Unset the UCI or error + if self.rmempty or self.optional then + self:remove(section) + 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:cfgvalue(s) or self:formcreated(s) then + scope = scope or {} + scope.section = s + Node.render(self, scope) + end +end + +-- Return the UCI value of this object +function AbstractValue.cfgvalue(self, section) + return self.map:get(section, self.option) +end + +-- Validate the form value +function AbstractValue.validate(self, value) + return value +end + +-- 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 + isnumber: The value must be a valid (floating point) number + isinteger: The value must be a valid integer + ispositive: The value must be positive (and a number) +]]-- +Value = class(AbstractValue) + +function Value.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/value" + + self.maxlength = nil + self.isnumber = false + self.isinteger = false +end + +-- This validation is a bit more complex +function Value.validate(self, val) + if self.maxlength and tostring(val):len() > self.maxlength then + val = nil + end + + return luci.util.validate(val, self.isnumber, self.isinteger) +end + + +-- DummyValue - This does nothing except being there +DummyValue = class(AbstractValue) + +function DummyValue.__init__(self, map, ...) + AbstractValue.__init__(self, map, ...) + self.template = "cbi/dvalue" + self.value = nil +end + +function DummyValue.parse(self) + +end + +function DummyValue.render(self, s) + luci.template.render(self.template, {self=self, section=s}) +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" +end + +-- A flag can only have two states: set or unset +function Flag.parse(self, section) + local fvalue = self:formvalue(section) + + if fvalue then + fvalue = self.enabled + else + fvalue = self.disabled + end + + if fvalue == self.enabled or (not self.optional and not self.rmempty) then + if not(fvalue == self:cfgvalue(section)) then + self:write(section, fvalue) + end + else + self:remove(section) + end +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.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +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.value(self, key, val) + 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) + if not(type(val) == "string") then + return nil + end + + local result = "" + + for value in val:gmatch("[^\n]+") do + if luci.util.contains(self.keylist, value) then + result = result .. self.delimiter .. value + end + end + + if result:len() > 0 then + return result:sub(self.delimiter:len() + 1) + else + return nil + end +end
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/dvalue.htm b/libs/cbi/src/view/cbi/dvalue.htm new file mode 100644 index 000000000..f54667def --- /dev/null +++ b/libs/cbi/src/view/cbi/dvalue.htm @@ -0,0 +1,12 @@ +<%+cbi/valueheader%> +<% if self.value then + if type(self.value) == "function" then %> + <%=self:value(section)%> +<% else %> + <%=self.value%> +<% end +else %> + <%=self:cfgvalue(section)%> +<% end %> + +<%+cbi/valuefooter%> diff --git a/libs/cbi/src/view/cbi/footer.htm b/libs/cbi/src/view/cbi/footer.htm new file mode 100644 index 000000000..2acf710cd --- /dev/null +++ b/libs/cbi/src/view/cbi/footer.htm @@ -0,0 +1,7 @@ + <div> + <input type="submit" value="<%:save Speichern%>" /> + <input type="reset" value="<%:reset Zurücksetzen%>" /> + <script type="text/javascript">cbi_d_init();</script> + </div> + </form> +<%+footer%>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/full_valuefooter.htm b/libs/cbi/src/view/cbi/full_valuefooter.htm new file mode 100644 index 000000000..6151a3a66 --- /dev/null +++ b/libs/cbi/src/view/cbi/full_valuefooter.htm @@ -0,0 +1,8 @@ + <div class="cbi-value-description"><%=self.description%> </div> + </div> + <% if self.tag_invalid[section] then %><div class="cbi-error"><%:cbi_invalid Fehler: Ungültige Eingabe%></div><% end %> + </div> + <% if #self.deps > 0 then %><script type="text/javascript"> + <% for j, d in ipairs(self.deps) do %>cbi_d_add("cbi-<%=self.config.."-"..section.."-"..self.option%>", "cbid.<%=self.config.."."..section.."."..d.field%>", "<%=d.value%>"); + <% end %> + </script><% end %>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/full_valueheader.htm b/libs/cbi/src/view/cbi/full_valueheader.htm new file mode 100644 index 000000000..062efa2dd --- /dev/null +++ b/libs/cbi/src/view/cbi/full_valueheader.htm @@ -0,0 +1,3 @@ + <div class="cbi-value" id="cbi-<%=self.config.."-"..section.."-"..self.option%>"> + <div class="cbi-value-title"><%=self.title%></div> + <div class="cbi-value-field">
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/fvalue.htm b/libs/cbi/src/view/cbi/fvalue.htm new file mode 100644 index 000000000..b609f1d4f --- /dev/null +++ b/libs/cbi/src/view/cbi/fvalue.htm @@ -0,0 +1,3 @@ +<%+cbi/valueheader%> + <input onchange="cbi_d_update(this.id)" type="checkbox" id="cbid.<%=self.config.."."..section.."."..self.option%>" name="cbid.<%=self.config.."."..section.."."..self.option%>"<% if self:cfgvalue(section) == self.enabled then %> checked="checked"<% end %> value="1" /> +<%+cbi/valuefooter%>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/header.htm b/libs/cbi/src/view/cbi/header.htm new file mode 100644 index 000000000..4229aaf0d --- /dev/null +++ b/libs/cbi/src/view/cbi/header.htm @@ -0,0 +1,7 @@ +<%+header%> + <form method="post" action="<%=luci.http.env.REQUEST_URI%>"> + <div> + <script type="text/javascript" src="<%=resource%>/cbi.js"></script> + <input type="hidden" name="cbi.submit" value="1" /> + <input type="submit" value="<%:save Speichern%>" class="hidden" /> + </div> diff --git a/libs/cbi/src/view/cbi/lvalue.htm b/libs/cbi/src/view/cbi/lvalue.htm new file mode 100644 index 000000000..f1ae5a093 --- /dev/null +++ b/libs/cbi/src/view/cbi/lvalue.htm @@ -0,0 +1,16 @@ +<%+cbi/valueheader%> +<% if self.widget == "select" then %> + <select onchange="cbi_d_update(this.id)" id="cbid.<%=self.config.."."..section.."."..self.option%>" name="cbid.<%=self.config.."."..section.."."..self.option%>"<% if self.size then %> size="<%=self.size%>"<% end %>> +<%for i, key in pairs(self.keylist) do%> + <option<% if self:cfgvalue(section) == key then %> selected="selected"<% end %> value="<%=key%>"><%=self.vallist[i]%></option> +<% end %> + </select> +<% elseif self.widget == "radio" then + local c = 0; + for i, key in pairs(self.keylist) do + c = c + 1%> + <%=self.vallist[i]%><input type="radio" name="cbid.<%=self.config.."."..section.."."..self.option%>"<% if self:cfgvalue(section) == key then %> checked="checked"<% end %> value="<%=key%>" /> +<% if c == self.size then c = 0 %><br /> +<% end end %> +<% end %> +<%+cbi/valuefooter%>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/map.htm b/libs/cbi/src/view/cbi/map.htm new file mode 100644 index 000000000..835393c1c --- /dev/null +++ b/libs/cbi/src/view/cbi/map.htm @@ -0,0 +1,6 @@ + <div class="cbi-map" id="cbi-<%=self.config%>"> + <h1><%=self.title%></h1> + <div class="cbi-map-descr"><%=self.description%></div> +<% self:render_children() %> + <br /> + </div> diff --git a/libs/cbi/src/view/cbi/mvalue.htm b/libs/cbi/src/view/cbi/mvalue.htm new file mode 100644 index 000000000..bed66e569 --- /dev/null +++ b/libs/cbi/src/view/cbi/mvalue.htm @@ -0,0 +1,19 @@ +<% +local v = self:valuelist(section) +%> +<%+cbi/valueheader%> +<% if self.widget == "select" then %> + <select multiple="multiple" name="cbid.<%=self.config.."."..section.."."..self.option%>[]"<% if self.size then %> size="<%=self.size%>"<% end %>> +<%for i, key in pairs(self.keylist) do %> + <option<% if luci.util.contains(v, key) then %> selected="selected"<% end %> value="<%=key%>"><%=self.vallist[i]%></option> +<% end %> + </select> +<% elseif self.widget == "checkbox" then + local c = 0; + for i, key in pairs(self.keylist) do + c = c + 1%> + <%=self.vallist[i]%><input type="checkbox" name="cbid.<%=self.config.."."..section.."."..self.option%>[]"<% if luci.util.contains(v, key) then %> checked="checked"<% end %> value="<%=key%>" /> +<% if c == self.size then c = 0 %><br /> +<% end end %> +<% end %> +<%+cbi/valuefooter%>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/nsection.htm b/libs/cbi/src/view/cbi/nsection.htm new file mode 100644 index 000000000..fff597ad0 --- /dev/null +++ b/libs/cbi/src/view/cbi/nsection.htm @@ -0,0 +1,20 @@ +<% if self:cfgvalue(self.section) then +section = self.section %> + <div class="cbi-section" id="cbi-<%=self.config%>-<%=section%>"> + <h2><%=self.title%></h2> + <div class="cbi-section-descr"><%=self.description%></div> + <% if self.addremove then %><div class="cbi-section-remove right"> + <input type="submit" name="cbi.rns.<%=self.config%>.<%=section%>" value="<%:cbi_del Eintrag entfernen%>" /> + </div><% end %> +<div class="cbi-section-node" id="cbi-<%=self.config%>-<%=section%>"> +<%+cbi/ucisection%> +</div> +<br /> + </div> +<% elseif self.addremove then %> + <div class="cbi-section" id="cbi-<%=self.config%>-<%=self.section%>"> + <h2><%=self.title%></h2> + <div class="cbi-section-descr"><%=self.description%></div> + <input type="submit" name="cbi.cns.<%=self.config%>.<%=self.section%>" value="<%:cbi_add Eintrag anlegen%>" /> + </div> +<% end %> diff --git a/libs/cbi/src/view/cbi/tblsection.htm b/libs/cbi/src/view/cbi/tblsection.htm new file mode 100644 index 000000000..df16efbed --- /dev/null +++ b/libs/cbi/src/view/cbi/tblsection.htm @@ -0,0 +1,39 @@ + <div class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>"> + <h2><%=self.title%></h2> + <div class="cbi-section-descr"><%=self.description%></div> + <div class="cbi-section-node"> + <div class="cbi-section-row"> +<% for i, k in pairs(self.children) do %> + <div class="cbi-section-row-head"><%=k.title%></div> +<% end %> + </div> + <div class="cbi-section-row"> +<% for i, k in pairs(self.children) do %> + <div class="cbi-section-row-descr"><%=k.description%></div> +<% end %> + </div> +<% for i, k in ipairs(self:cfgsections()) do%> + <% if not self.anonymous then %><h3 class="table-cell"><%=k%></h3><% end %> +<% +section = k +scope = {valueheader = "cbi/tiny_valueheader", valuefooter = "cbi/tiny_valuefooter"} +%> +<div class="cbi-section-row" id="cbi-<%=self.config%>-<%=section%>"> +<%+cbi/ucisection%> + <% if self.addremove then %><div class="cbi-section-remove table-cell"> + <input type="submit" name="cbi.rts.<%=self.config%>.<%=k%>" value="X" /> + </div><% end %> +</div> +<% end %> +<% if self.addremove then %> + <div class="cbi-section-create"> + <% if self.anonymous then %> + <input type="submit" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>" value="<%:cbi_add Eintrag hinzufügen%>" /> + <% else %> + <input type="text" class="cbi-section-create-name" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>" /> + <input type="submit" value="<%:cbi_add Eintrag hinzufügen%>" /> + <% end %><% if self.err_invalid then %><div class="cbi-error"><%:cbi_invalid Fehler: Ungültige Eingabe%></div><% end %> + </div> + </div> +<% end %> + </div> diff --git a/libs/cbi/src/view/cbi/tiny_valuefooter.htm b/libs/cbi/src/view/cbi/tiny_valuefooter.htm new file mode 100644 index 000000000..e65ebb6c0 --- /dev/null +++ b/libs/cbi/src/view/cbi/tiny_valuefooter.htm @@ -0,0 +1,6 @@ + <% if self.tag_invalid[section] then %><div class="cbi-error"><%:cbi_invalid Fehler: Ungültige Eingabe%></div><% end %> + </div> + <% if #self.deps > 0 then %><script type="text/javascript"> + <% for j, d in ipairs(self.deps) do %>cbi_d_add("cbi-<%=self.config.."-"..section.."-"..self.option%>", "cbid.<%=self.config.."."..section.."."..d.field%>", "<%=d.value%>"); + <% end %> + </script><% end %>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/tiny_valueheader.htm b/libs/cbi/src/view/cbi/tiny_valueheader.htm new file mode 100644 index 000000000..b9b26bd6a --- /dev/null +++ b/libs/cbi/src/view/cbi/tiny_valueheader.htm @@ -0,0 +1 @@ + <div class="cbi-value-field" id="cbi-<%=self.config.."-"..section.."-"..self.option%>"> diff --git a/libs/cbi/src/view/cbi/tsection.htm b/libs/cbi/src/view/cbi/tsection.htm new file mode 100644 index 000000000..37b18b5d4 --- /dev/null +++ b/libs/cbi/src/view/cbi/tsection.htm @@ -0,0 +1,25 @@ + <div class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>"> + <h2><%=self.title%></h2> + <div class="cbi-section-descr"><%=self.description%></div> +<% for i, k in ipairs(self:cfgsections()) do%> + <% if self.addremove then %><div class="cbi-section-remove right"> + <input type="submit" name="cbi.rts.<%=self.config%>.<%=k%>" value="<%:cbi_del Eintrag entfernen%>" /> + </div><% end %> + <% if not self.anonymous then %><h3><%=k%></h3><% end %> +<% section = k %> +<div class="cbi-section-node" id="cbi-<%=self.config%>-<%=section%>"> +<%+cbi/ucisection%> +</div> +<br /> +<% end %> +<% if self.addremove then %> + <div class="cbi-section-create"> + <% if self.anonymous then %> + <input type="submit" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>" value="<%:cbi_add Eintrag hinzufügen%>" /> + <% else %> + <input type="text" class="cbi-section-create-name" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>" /> + <input type="submit" value="<%:cbi_add Eintrag hinzufügen%>" /> + <% end %><% if self.err_invalid then %><div class="cbi-error"><%:cbi_invalid Fehler: Ungültige Eingabe%></div><% end %> + </div> +<% end %> + </div> diff --git a/libs/cbi/src/view/cbi/ucisection.htm b/libs/cbi/src/view/cbi/ucisection.htm new file mode 100644 index 000000000..0abc37e7c --- /dev/null +++ b/libs/cbi/src/view/cbi/ucisection.htm @@ -0,0 +1,20 @@ +<% self:render_children(section, scope or {}) %> + <% if #self.optionals[section] > 0 or self.dynamic then %> + <div class="cbi-optionals"> + <% if self.dynamic then %> + <input type="text" name="cbi.opt.<%=self.config%>.<%=section%>" /> + <% else %> + <select name="cbi.opt.<%=self.config%>.<%=section%>"> + <option><%:cbi_addopt -- Feld --%></option> + <% for key, val in pairs(self.optionals[section]) do %> + <option id="cbi-<%=self.config.."-"..section.."-"..val.option%>" value="<%=val.option%>"><%=val.title%></option> + <% end %> + </select> + <script type="text/javascript"><% for key, val in pairs(self.optionals[section]) do %> + <% if #val.deps > 0 then %><% for j, d in ipairs(val.deps) do %>cbi_d_add("cbi-<%=self.config.."-"..section.."-"..val.option%>", "cbid.<%=self.config.."."..section.."."..d.field%>", "<%=d.value%>"); + <% end %><% end %> + <% end %></script> + <% end %> + <input type="submit" value="<%:add hinzufügen%>" /> + </div> + <% end %>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/value.htm b/libs/cbi/src/view/cbi/value.htm new file mode 100644 index 000000000..31bf38f77 --- /dev/null +++ b/libs/cbi/src/view/cbi/value.htm @@ -0,0 +1,3 @@ +<%+cbi/valueheader%> + <input type="text" onchange="cbi_d_update(this.id)" <% if self.size then %>size="<%=self.size%>" <% end %><% if self.maxlength then %>maxlength="<%=self.maxlength%>" <% end %>name="cbid.<%=self.config.."."..section.."."..self.option%>" id="cbid.<%=self.config.."."..section.."."..self.option%>" value="<%=self:cfgvalue(section)%>" /> +<%+cbi/valuefooter%> diff --git a/libs/cbi/src/view/cbi/valuefooter.htm b/libs/cbi/src/view/cbi/valuefooter.htm new file mode 100644 index 000000000..bc9d1b127 --- /dev/null +++ b/libs/cbi/src/view/cbi/valuefooter.htm @@ -0,0 +1,5 @@ +<% if valuefooter then + include(valuefooter) +else + include("cbi/full_valuefooter") +end %>
\ No newline at end of file diff --git a/libs/cbi/src/view/cbi/valueheader.htm b/libs/cbi/src/view/cbi/valueheader.htm new file mode 100644 index 000000000..8d9802f57 --- /dev/null +++ b/libs/cbi/src/view/cbi/valueheader.htm @@ -0,0 +1,5 @@ +<% if valueheader then + include(valueheader) +else + include("cbi/full_valueheader") +end %>
\ No newline at end of file diff --git a/libs/core/Makefile b/libs/core/Makefile new file mode 100644 index 000000000..f7fac7740 --- /dev/null +++ b/libs/core/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk diff --git a/libs/core/src/bits.lua b/libs/core/src/bits.lua new file mode 100644 index 000000000..13b4c3066 --- /dev/null +++ b/libs/core/src/bits.lua @@ -0,0 +1,542 @@ +--[[ +/* + * Copyright (c) 2007 Tim Kelly/Dialectronics + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT + * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR + * THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +--]] + +--[[ +/* + * Copyright (c) 2007 Tim Kelly/Dialectronics + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT + * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR + * THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +--]] + +module("luci.bits", package.seeall); + +local hex2bin = { + ["0"] = "0000", + ["1"] = "0001", + ["2"] = "0010", + ["3"] = "0011", + ["4"] = "0100", + ["5"] = "0101", + ["6"] = "0110", + ["7"] = "0111", + ["8"] = "1000", + ["9"] = "1001", + ["a"] = "1010", + ["b"] = "1011", + ["c"] = "1100", + ["d"] = "1101", + ["e"] = "1110", + ["f"] = "1111" + } + + + +local bin2hex = { + ["0000"] = "0", + ["0001"] = "1", + ["0010"] = "2", + ["0011"] = "3", + ["0100"] = "4", + ["0101"] = "5", + ["0110"] = "6", + ["0111"] = "7", + ["1000"] = "8", + ["1001"] = "9", + ["1010"] = "A", + ["1011"] = "B", + ["1100"] = "C", + ["1101"] = "D", + ["1110"] = "E", + ["1111"] = "F" + } + +--[[ +local dec2hex = { + ["0"] = "0", + ["1"] = "1", + ["2"] = "2", + ["3"] = "3", + ["4"] = "4", + ["5"] = "5", + ["6"] = "6", + ["7"] = "7", + ["8"] = "8", + ["9"] = "9", + ["10"] = "A", + ["11"] = "B", + ["12"] = "C", + ["13"] = "D", + ["14"] = "E", + ["15"] = "F" + } +--]] + + +-- These functions are big-endian and take up to 32 bits + +-- Hex2Bin +-- Bin2Hex +-- Hex2Dec +-- Dec2Hex +-- Bin2Dec +-- Dec2Bin + + +function Hex2Bin(s) + +-- s -> hexadecimal string + +local ret = "" +local i = 0 + + + for i in string.gfind(s, ".") do + i = string.lower(i) + + ret = ret..hex2bin[i] + + end + + return ret +end + + +function Bin2Hex(s) + +-- s -> binary string + +local l = 0 +local h = "" +local b = "" +local rem + +l = string.len(s) +rem = l % 4 +l = l-1 +h = "" + + -- need to prepend zeros to eliminate mod 4 + if (rem > 0) then + s = string.rep("0", 4 - rem)..s + end + + for i = 1, l, 4 do + b = string.sub(s, i, i+3) + h = h..bin2hex[b] + end + + return h + +end + + +function Bin2Dec(s) + +-- s -> binary string + +local num = 0 +local ex = string.len(s) - 1 +local l = 0 + + l = ex + 1 + for i = 1, l do + b = string.sub(s, i, i) + if b == "1" then + num = num + 2^ex + end + ex = ex - 1 + end + + return string.format("%u", num) + +end + + + +function Dec2Bin(s, num) + +-- s -> Base10 string +-- num -> string length to extend to + +local n + + if (num == nil) then + n = 0 + else + n = num + end + + s = string.format("%x", s) + + s = Hex2Bin(s) + + while string.len(s) < n do + s = "0"..s + end + + return s + +end + + + + +function Hex2Dec(s) + +-- s -> hexadecimal string + +local s = Hex2Bin(s) + + return Bin2Dec(s) + +end + + + +function Dec2Hex(s) + +-- s -> Base10 string + + s = string.format("%x", s) + + return s + +end + + + + +-- These functions are big-endian and will extend to 32 bits + +-- BMAnd +-- BMNAnd +-- BMOr +-- BMXOr +-- BMNot + + +function BMAnd(v, m) + +-- v -> hex string to be masked +-- m -> hex string mask + +-- s -> hex string as masked + +-- bv -> binary string of v +-- bm -> binary string mask + +local bv = Hex2Bin(v) +local bm = Hex2Bin(m) + +local i = 0 +local s = "" + + while (string.len(bv) < 32) do + bv = "0000"..bv + end + + while (string.len(bm) < 32) do + bm = "0000"..bm + end + + + for i = 1, 32 do + cv = string.sub(bv, i, i) + cm = string.sub(bm, i, i) + if cv == cm then + if cv == "1" then + s = s.."1" + else + s = s.."0" + end + else + s = s.."0" + + end + end + + return Bin2Hex(s) + +end + + +function BMNAnd(v, m) + +-- v -> hex string to be masked +-- m -> hex string mask + +-- s -> hex string as masked + +-- bv -> binary string of v +-- bm -> binary string mask + +local bv = Hex2Bin(v) +local bm = Hex2Bin(m) + +local i = 0 +local s = "" + + while (string.len(bv) < 32) do + bv = "0000"..bv + end + + while (string.len(bm) < 32) do + bm = "0000"..bm + end + + + for i = 1, 32 do + cv = string.sub(bv, i, i) + cm = string.sub(bm, i, i) + if cv == cm then + if cv == "1" then + s = s.."0" + else + s = s.."1" + end + else + s = s.."1" + + end + end + + return Bin2Hex(s) + +end + + + +function BMOr(v, m) + +-- v -> hex string to be masked +-- m -> hex string mask + +-- s -> hex string as masked + +-- bv -> binary string of v +-- bm -> binary string mask + +local bv = Hex2Bin(v) +local bm = Hex2Bin(m) + +local i = 0 +local s = "" + + while (string.len(bv) < 32) do + bv = "0000"..bv + end + + while (string.len(bm) < 32) do + bm = "0000"..bm + end + + + for i = 1, 32 do + cv = string.sub(bv, i, i) + cm = string.sub(bm, i, i) + if cv == "1" then + s = s.."1" + elseif cm == "1" then + s = s.."1" + else + s = s.."0" + end + end + + return Bin2Hex(s) + +end + +function BMXOr(v, m) + +-- v -> hex string to be masked +-- m -> hex string mask + +-- s -> hex string as masked + +-- bv -> binary string of v +-- bm -> binary string mask + +local bv = Hex2Bin(v) +local bm = Hex2Bin(m) + +local i = 0 +local s = "" + + while (string.len(bv) < 32) do + bv = "0000"..bv + end + + while (string.len(bm) < 32) do + bm = "0000"..bm + end + + + for i = 1, 32 do + cv = string.sub(bv, i, i) + cm = string.sub(bm, i, i) + if cv == "1" then + if cm == "0" then + s = s.."1" + else + s = s.."0" + end + elseif cm == "1" then + if cv == "0" then + s = s.."1" + else + s = s.."0" + end + else + -- cv and cm == "0" + s = s.."0" + end + end + + return Bin2Hex(s) + +end + + +function BMNot(v, m) + +-- v -> hex string to be masked +-- m -> hex string mask + +-- s -> hex string as masked + +-- bv -> binary string of v +-- bm -> binary string mask + +local bv = Hex2Bin(v) +local bm = Hex2Bin(m) + +local i = 0 +local s = "" + + while (string.len(bv) < 32) do + bv = "0000"..bv + end + + while (string.len(bm) < 32) do + bm = "0000"..bm + end + + + for i = 1, 32 do + cv = string.sub(bv, i, i) + cm = string.sub(bm, i, i) + if cm == "1" then + if cv == "1" then + -- turn off + s = s.."0" + else + -- turn on + s = s.."1" + end + else + -- leave untouched + s = s..cv + + end + end + + return Bin2Hex(s) + +end + + +-- these functions shift right and left, adding zeros to lost or gained bits +-- returned values are 32 bits long + +-- BShRight(v, nb) +-- BShLeft(v, nb) + + +function BShRight(v, nb) + +-- v -> hexstring value to be shifted +-- nb -> number of bits to shift to the right + +-- s -> binary string of v + +local s = Hex2Bin(v) + + while (string.len(s) < 32) do + s = "0000"..s + end + + s = string.sub(s, 1, 32 - nb) + + while (string.len(s) < 32) do + s = "0"..s + end + + return Bin2Hex(s) + +end + +function BShLeft(v, nb) + +-- v -> hexstring value to be shifted +-- nb -> number of bits to shift to the right + +-- s -> binary string of v + +local s = Hex2Bin(v) + + while (string.len(s) < 32) do + s = "0000"..s + end + + s = string.sub(s, nb + 1, 32) + + while (string.len(s) < 32) do + s = s.."0" + end + + return Bin2Hex(s) + +end
\ No newline at end of file diff --git a/libs/core/src/debug.lua b/libs/core/src/debug.lua new file mode 100644 index 000000000..a56400f34 --- /dev/null +++ b/libs/core/src/debug.lua @@ -0,0 +1,2 @@ +module("luci.debug", package.seeall) +__file__ = debug.getinfo(1, 'S').source:sub(2)
\ No newline at end of file diff --git a/libs/core/src/fs.lua b/libs/core/src/fs.lua new file mode 100644 index 000000000..5c1f2a051 --- /dev/null +++ b/libs/core/src/fs.lua @@ -0,0 +1,129 @@ +--[[ +LuCI - Filesystem tools + +Description: +A module offering often needed filesystem manipulation functions + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.fs", package.seeall) + +require("posix") + +-- Glob +glob = posix.glob + +-- Checks whether a file exists +function isfile(filename) + local fp = io.open(filename, "r") + if fp then fp:close() end + return fp ~= nil +end + +-- Returns the content of file +function readfile(filename) + local fp, err = io.open(filename) + + if fp == nil then + return nil, err + end + + local data = fp:read("*a") + fp:close() + return data +end + +-- Writes given data to a file +function writefile(filename, data) + local fp, err = io.open(filename, "w") + + if fp == nil then + return nil, err + end + + fp:write(data) + fp:close() + + return true +end + +-- Returns the file modification date/time of "path" +function mtime(path) + return posix.stat(path, "mtime") +end + +-- basename wrapper +basename = posix.basename + +-- dirname wrapper +dirname = posix.dirname + +-- dir wrapper +dir = posix.dir + +-- wrapper for posix.mkdir +function mkdir(path, recursive) + if recursive then + local base = "." + + if path:sub(1,1) == "/" then + base = "" + path = path:gsub("^/+","") + end + + for elem in path:gmatch("([^/]+)/*") do + base = base .. "/" .. elem + + local stat = posix.stat( base ) + + if not stat then + local stat, errmsg, errno = posix.mkdir( base ) + + if type(stat) ~= "number" or stat ~= 0 then + return stat, errmsg, errno + end + else + if stat.type ~= "directory" then + return nil, base .. ": File exists", 17 + end + end + end + + return 0 + else + return posix.mkdir( path ) + end +end + +-- Alias for posix.rmdir +rmdir = posix.rmdir + +-- Alias for posix.stat +stat = posix.stat + +-- Alias for posix.chmod +chmod = posix.chmod + +-- Alias for posix.link +link = posix.link + +-- Alias for posix.unlink +unlink = posix.unlink diff --git a/libs/core/src/init.lua b/libs/core/src/init.lua new file mode 100644 index 000000000..ce52d0aad --- /dev/null +++ b/libs/core/src/init.lua @@ -0,0 +1,29 @@ +--[[ +LuCI - Lua Configuration Interface + +Description: +Main class + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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", package.seeall) + +__version__ = "0.5" +__appname__ = "LuCI" diff --git a/libs/core/src/model/ipkg.lua b/libs/core/src/model/ipkg.lua new file mode 100644 index 000000000..e95a2620a --- /dev/null +++ b/libs/core/src/model/ipkg.lua @@ -0,0 +1,140 @@ +--[[ +LuCI - IPKG wrapper library + +Description: +Wrapper for the ipkg Package manager + +Any return value of false or nil can be interpreted as an error + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.model.ipkg", package.seeall) +require("luci.sys") +require("luci.util") + +ipkg = "ipkg" + +-- Returns repository information +function info(pkg) + return _lookup("info", pkg) +end + +-- Returns a table with status information +function status(pkg) + return _lookup("status", pkg) +end + +-- Installs packages +function install(...) + return _action("install", ...) +end + +-- Returns whether a package is installed +function installed(pkg, ...) + local p = status(...)[pkg] + return (p and p.Status and p.Status.installed) +end + +-- Removes packages +function remove(...) + return _action("remove", ...) +end + +-- Updates package lists +function update() + return _action("update") +end + +-- Upgrades installed packages +function upgrade() + return _action("upgrade") +end + + +-- Internal action function +function _action(cmd, ...) + local pkg = "" + arg.n = nil + for k, v in pairs(arg) do + pkg = pkg .. " '" .. v:gsub("'", "") .. "'" + end + + local c = ipkg.." "..cmd.." "..pkg.." >/dev/null 2>&1" + local r = os.execute(c) + return (r == 0), r +end + +-- Internal lookup function +function _lookup(act, pkg) + local cmd = ipkg .. " " .. act + if pkg then + cmd = cmd .. " '" .. pkg:gsub("'", "") .. "'" + end + + return _parselist(luci.sys.exec(cmd .. " 2>/dev/null")) +end + +-- Internal parser function +function _parselist(rawdata) + if type(rawdata) ~= "string" then + error("IPKG: Invalid rawdata given") + end + + rawdata = luci.util.split(rawdata) + local data = {} + local c = {} + local l = nil + + for k, line in pairs(rawdata) do + if line:sub(1, 1) ~= " " then + local split = luci.util.split(line, ":", 1) + local key = nil + local val = nil + + if split[1] then + key = luci.util.trim(split[1]) + end + + if split[2] then + val = luci.util.trim(split[2]) + end + + if key and val then + if key == "Package" then + c = {Package = val} + data[val] = c + elseif key == "Status" then + c.Status = {} + for i, j in pairs(luci.util.split(val, " ")) do + c.Status[j] = true + end + else + c[key] = val + end + l = key + end + else + -- Multi-line field + c[l] = c[l] .. "\n" .. line:sub(2) + end + end + + return data +end
\ No newline at end of file diff --git a/libs/core/src/model/uci.lua b/libs/core/src/model/uci.lua new file mode 100644 index 000000000..39354bed1 --- /dev/null +++ b/libs/core/src/model/uci.lua @@ -0,0 +1,92 @@ +--[[ +LuCI - UCI mpdel + +Description: +Generalized UCI model + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.model.uci", package.seeall) + +-- Default savedir +savedir = "/tmp/.uci" + +-- Test whether to load libuci-Wrapper or /sbin/uci-Wrapper +if pcall(require, "uci") then + Session = require("luci.model.uci.libuci").Session +else + Session = require("luci.model.uci.wrapper").Session +end + +-- The default Session +local default = Session() +local state = Session("/var/state") + +-- The state Session +function StateSession() + return state +end + + +-- Wrapper for "uci add" +function add(...) + return default:add(...) +end + + +-- Wrapper for "uci changes" +function changes(...) + return default:changes(...) +end + +-- Wrapper for "uci commit" +function commit(...) + return default:commit(...) +end + + +-- Wrapper for "uci del" +function del(...) + return default:del(...) +end + + +-- Wrapper for "uci get" +function get(...) + return default:get(...) +end + + +-- Wrapper for "uci revert" +function revert(...) + return default:revert(...) +end + + +-- Wrapper for "uci show" +function sections(...) + return default:sections(...) +end + + +-- Wrapper for "uci set" +function set(...) + return default:set(...) +end
\ No newline at end of file diff --git a/libs/core/src/model/uci/libuci.lua b/libs/core/src/model/uci/libuci.lua new file mode 100644 index 000000000..9a1112500 --- /dev/null +++ b/libs/core/src/model/uci/libuci.lua @@ -0,0 +1,193 @@ +--[[ +LuCI - UCI libuci wrapper + +Description: +Wrapper for the libuci Lua bindings + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.model.uci.libuci", package.seeall) + +require("uci") +require("luci.util") +require("luci.sys") + +-- Session class +Session = luci.util.class() + +-- Session constructor +function Session.__init__(self, savedir) + self.ucicmd = savedir and "uci -P " .. savedir or "uci" + self.savedir = savedir or luci.model.uci.savedir +end + +function Session.add(self, config, section_type) + return self:_uci("add " .. _path(config) .. " " .. _path(section_type)) +end + +function Session.changes(self, config) + return self:_uci("changes " .. _path(config)) +end + +function Session.commit(self, config) + self:t_load(config) + return self:t_commit(config) +end + +function Session.del(self, config, section, option) + return self:_uci2("del " .. _path(config, section, option)) +end + +function Session.get(self, config, section, option) + self:t_load(config) + return self:t_get(config, section, option) +end + +function Session.revert(self, config) + self:t_load(config) + return self:t_revert(config) +end + +function Session.sections(self, config) + self:t_load(config) + return self:t_sections(config) +end + +function Session.set(self, config, section, option, value) + self:t_load(config) + return self:t_set(config, section, option, value) and self:t_save(config) +end + +function Session.synchronize(self) + return uci.set_savedir(self.savedir) +end + + +-- UCI-Transactions + +function Session.t_load(self, config) + return self:synchronize() and uci.load(config) +end + +function Session.t_save(self, config) + return uci.save(config) +end + +function Session.t_add(self, config, type) + self:t_save(config) + local r = self:add(config, type) + self:t_load(config) + return r +end + +function Session.t_commit(self, config) + return uci.commit(config) +end + +function Session.t_del(self, config, section, option) + self:t_save(config) + local r = self:del(config, section, option) + self:t_load(config) + return r +end + +function Session.t_get(self, config, section, option) + if option then + return uci.get(config, section, option) + else + return uci.get(config, section) + end +end + +function Session.t_revert(self, config) + return uci.revert(config) +end + +function Session.t_sections(self, config) + local raw = uci.get_all(config) + if not raw then + return nil + end + + local s = {} + local o = {} + + for i, sec in ipairs(raw) do + table.insert(o, sec.name) + + s[sec.name] = sec.options + s[sec.name][".type"] = sec.type + end + + return s, o +end + +function Session.t_set(self, config, section, option, value) + if option then + return uci.set(config.."."..section.."."..option.."="..value) + else + return uci.set(config.."."..section.."="..value) + end +end + +-- Internal functions -- + + +function Session._uci(self, cmd) + local res = luci.sys.exec(self.ucicmd .. " 2>/dev/null " .. cmd) + + if res:len() == 0 then + return nil + else + return res:sub(1, res:len()-1) + end +end + +function Session._uci2(self, cmd) + local res = luci.sys.exec(self.ucicmd .. " 2>&1 " .. cmd) + + if res:len() > 0 then + return false, res + else + return true + end +end + +-- Build path (config.section.option=value) and prevent command injection +function _path(...) + local result = "" + + -- Not using ipairs because it is not reliable in case of nil arguments + arg.n = nil + for k,v in pairs(arg) do + if v then + v = tostring(v) + if k == 1 then + result = "'" .. v:gsub("['.]", "") .. "'" + elseif k < 4 then + result = result .. ".'" .. v:gsub("['.]", "") .. "'" + elseif k == 4 then + result = result .. "='" .. v:gsub("'", "") .. "'" + end + end + end + return result +end
\ No newline at end of file diff --git a/libs/core/src/model/uci/wrapper.lua b/libs/core/src/model/uci/wrapper.lua new file mode 100644 index 000000000..e063b272c --- /dev/null +++ b/libs/core/src/model/uci/wrapper.lua @@ -0,0 +1,171 @@ +--[[ +LuCI - UCI wrapper library + +Description: +Wrapper for the /sbin/uci application, syntax of implemented functions +is comparable to the syntax of the uci application + +Any return value of false or nil can be interpreted as an error + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.model.uci.wrapper", package.seeall) + +require("luci.util") +require("luci.sys") + +-- Session class +Session = luci.util.class() + +-- Session constructor +function Session.__init__(self, savedir) + self.ucicmd = savedir and "uci -P " .. savedir or "uci" +end + +function Session.add(self, config, section_type) + return self:_uci("add " .. _path(config) .. " " .. _path(section_type)) +end + +function Session.changes(self, config) + return self:_uci("changes " .. _path(config)) +end + +function Session.commit(self, config) + return self:_uci2("commit " .. _path(config)) +end + +function Session.del(self, config, section, option) + return self:_uci2("del " .. _path(config, section, option)) +end + +function Session.get(self, config, section, option) + return self:_uci("get " .. _path(config, section, option)) +end + +function Session.revert(self, config) + return self:_uci2("revert " .. _path(config)) +end + +function Session.sections(self, config) + if not config then + return nil + end + + local r1, r2 = self:_uci3("show " .. _path(config)) + if type(r1) == "table" then + return r1, r2 + else + return nil, r2 + end +end + +function Session.set(self, config, section, option, value) + return self:_uci2("set " .. _path(config, section, option, value)) +end + +function Session.synchronize(self) end + +-- Dummy transaction functions + +function Session.t_load(self) end +function Session.t_save(self) end + +Session.t_add = Session.add +Session.t_commit = Session.commit +Session.t_del = Session.del +Session.t_get = Session.get +Session.t_revert = Session.revert +Session.t_sections = Session.sections +Session.t_set = Session.set + + + + + +-- Internal functions -- + + +function Session._uci(self, cmd) + local res = luci.sys.exec(self.ucicmd .. " 2>/dev/null " .. cmd) + + if res:len() == 0 then + return nil + else + return res:sub(1, res:len()-1) + end +end + +function Session._uci2(self, cmd) + local res = luci.sys.exec(self.ucicmd .. " 2>&1 " .. cmd) + + if res:len() > 0 then + return false, res + else + return true + end +end + +function Session._uci3(self, cmd) + local res = luci.sys.execl(self.ucicmd .. " 2>&1 " .. cmd) + if res[1] and res[1]:sub(1, self.ucicmd:len()+1) == self.ucicmd..":" then + return nil, res[1] + end + + local tbl = {} + local ord = {} + + for k,line in pairs(res) do + c, s, t = line:match("^([^.]-)%.([^.]-)=(.-)$") + if c then + tbl[s] = {} + table.insert(ord, s) + tbl[s][".type"] = t + end + + c, s, o, v = line:match("^([^.]-)%.([^.]-)%.([^.]-)=(.-)$") + if c then + tbl[s][o] = v + end + end + + return tbl, ord +end + +-- Build path (config.section.option=value) and prevent command injection +function _path(...) + local result = "" + + -- Not using ipairs because it is not reliable in case of nil arguments + arg.n = nil + for k,v in pairs(arg) do + if v then + v = tostring(v) + if k == 1 then + result = "'" .. v:gsub("['.]", "") .. "'" + elseif k < 4 then + result = result .. ".'" .. v:gsub("['.]", "") .. "'" + elseif k == 4 then + result = result .. "='" .. v:gsub("'", "") .. "'" + end + end + end + return result +end
\ No newline at end of file diff --git a/libs/core/src/sys.lua b/libs/core/src/sys.lua new file mode 100644 index 000000000..0399d0e5f --- /dev/null +++ b/libs/core/src/sys.lua @@ -0,0 +1,371 @@ +--[[ +LuCI - System library + +Description: +Utilities for interaction with the Linux system + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.sys", package.seeall) +require("posix") +require("luci.bits") +require("luci.util") +require("luci.fs") + +-- Returns whether a system is bigendian +function bigendian() + local fp = io.open("/bin/sh") + fp:seek("set", 5) + local be = (fp:read(1):byte() ~= 1) + fp:close() + return be +end + +-- Runs "command" and returns its output +function exec(command) + local pp = io.popen(command) + local data = pp:read("*a") + pp:close() + + return data +end + +-- Runs "command" and returns its output as a array of lines +function execl(command) + local pp = io.popen(command) + local line = "" + local data = {} + + while true do + line = pp:read() + if (line == nil) then break end + table.insert(data, line) + end + pp:close() + + return data +end + +-- Uses "luci-flash" to flash a new image file to the system +function flash(image, kpattern) + local cmd = "luci-flash " + if kpattern then + cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' " + end + cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1" + + return os.execute(cmd) +end + +-- Returns the hostname +function hostname() + return io.lines("/proc/sys/kernel/hostname")() +end + +-- Returns the contents of a documented referred by an URL +function httpget(url) + return exec("wget -qO- '"..url:gsub("'", "").."'") +end + +-- Returns the FFLuci-Basedir +function libpath() + return luci.fs.dirname(require("luci.debug").__file__) +end + +-- Returns the load average +function loadavg() + local loadavg = io.lines("/proc/loadavg")() + return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$") +end + +-- Reboots the system +function reboot() + return os.execute("reboot >/dev/null 2>&1") +end + +-- Returns the system type, cpu name, and installed physical memory +function sysinfo() + local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null" + local c2 = "uname -m 2>/dev/null" + local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null" + local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null" + local c5 = "cat /proc/meminfo|grep MemTotal|cut -d: -f2 2>/dev/null" + + local s = luci.util.trim(exec(c1)) + local m = "" + local r = "" + + if s == "" then + s = luci.util.trim(exec(c2)) + m = luci.util.trim(exec(c3)) + else + m = luci.util.trim(exec(c4)) + end + + r = luci.util.trim(exec(c5)) + + return s, m, r +end + +-- Reads the syslog +function syslog() + return exec("logread") +end + + +group = {} +group.getgroup = posix.getgroup + +net = {} +-- Returns the ARP-Table +function net.arptable() + return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+") +end + +-- Returns whether an IP-Adress belongs to a certain net +function net.belongs(ip, ipnet, prefix) + return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix)) +end + +-- Detect the default route +function net.defaultroute() + local routes = net.routes() + local route = nil + + for i, r in pairs(luci.sys.net.routes()) do + if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then + route = r + end + end + + return route +end + +-- Returns all available network interfaces +function net.devices() + local devices = {} + for line in io.lines("/proc/net/dev") do + table.insert(devices, line:match(" *(.-):")) + end + return devices +end + +-- Returns the MAC-Address belonging to the given IP-Address +function net.ip4mac(ip) + local mac = nil + + for i, l in ipairs(net.arptable()) do + if l["IP address"] == ip then + mac = l["HW address"] + end + end + + return mac +end + +-- Returns the prefix to a given netmask +function net.mask4prefix(mask) + local bin = net.ip4bin(mask) + + if not bin then + return nil + end + + return #luci.util.split(bin, "1")-1 +end + +-- Returns the kernel routing table +function net.routes() + return _parse_delimited_table(io.lines("/proc/net/route")) +end + +-- Returns the numeric IP to a given hexstring +function net.hexip4(hex, be) + if #hex ~= 8 then + return nil + end + + be = be or bigendian() + + local hexdec = luci.bits.Hex2Dec + + local ip = "" + if be then + ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "." + ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." + ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." + ip = ip .. tostring(hexdec(hex:sub(7,8))) + else + ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "." + ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." + ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." + ip = ip .. tostring(hexdec(hex:sub(1,2))) + end + + return ip +end + +-- Returns the binary IP to a given IP +function net.ip4bin(ip) + local parts = luci.util.split(ip, '.') + if #parts ~= 4 then + return nil + end + + local decbin = luci.bits.Dec2Bin + + local bin = "" + bin = bin .. decbin(parts[1], 8) + bin = bin .. decbin(parts[2], 8) + bin = bin .. decbin(parts[3], 8) + bin = bin .. decbin(parts[4], 8) + + return bin +end + +-- Tests whether a host is pingable +function net.pingtest(host) + return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1") +end + + +process = {} +process.info = posix.getpid + +-- Sets the gid of a process +function process.setgroup(pid, gid) + return posix.setpid("g", pid, gid) +end + +-- Sets the uid of a process +function process.setuser(pid, uid) + return posix.setpid("u", pid, uid) +end + +user = {} +-- returns user information to a given uid +user.getuser = posix.getpasswd + +-- Changes the user password of given user +function user.setpasswd(user, pwd) + if pwd then + pwd = pwd:gsub("'", "") + end + + if user then + user = user:gsub("'", "") + end + + local cmd = "(echo '"..pwd.."';sleep 1;echo '"..pwd.."')|" + cmd = cmd .. "passwd '"..user.."' >/dev/null 2>&1" + return os.execute(cmd) +end + + +wifi = {} + +function wifi.getiwconfig() + local cnt = exec("/usr/sbin/iwconfig 2>/dev/null") + local iwc = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do + local k = l:match("^(.-) ") + l = l:gsub("^(.-) +", "", 1) + if k then + iwc[k] = _parse_mixed_record(l) + end + end + + return iwc +end + +function wifi.iwscan() + local cnt = exec("iwlist scan 2>/dev/null") + local iws = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do + local k = l:match("^(.-) ") + l = l:gsub("^[^\n]+", "", 1) + l = luci.util.trim(l) + if k then + iws[k] = {} + for j, c in pairs(luci.util.split(l, "\n Cell")) do + c = c:gsub("^(.-)- ", "", 1) + c = luci.util.split(c, "\n", 7) + c = table.concat(c, "\n", 1) + table.insert(iws[k], _parse_mixed_record(c)) + end + end + end + + return iws +end + + +-- Internal functions + +function _parse_delimited_table(iter, delimiter) + delimiter = delimiter or "%s+" + + local data = {} + local trim = luci.util.trim + local split = luci.util.split + + local keys = split(trim(iter()), delimiter, nil, true) + for i, j in pairs(keys) do + keys[i] = trim(keys[i]) + end + + for line in iter do + local row = {} + line = trim(line) + if #line > 0 then + for i, j in pairs(split(line, delimiter, nil, true)) do + if keys[i] then + row[keys[i]] = j + end + end + end + table.insert(data, row) + end + + return data +end + +function _parse_mixed_record(cnt) + local data = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do + for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do + local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*') + + if k then + if x == "" then + table.insert(data, k) + else + data[k] = v + end + end + end + end + + return data +end
\ No newline at end of file diff --git a/libs/core/src/sys/iptparser.lua b/libs/core/src/sys/iptparser.lua new file mode 100644 index 000000000..6450c3072 --- /dev/null +++ b/libs/core/src/sys/iptparser.lua @@ -0,0 +1,245 @@ +--[[ +LuCI - Iptables parser and query library + +Copyright 2008 Jo-Philipp Wich <freifunk@wwsnet.net> + +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. + +$Id$ + +]]-- + +module("luci.sys.iptparser", package.seeall) +require("luci.sys") +require("luci.util") + + +IptParser = luci.util.class() + +--[[ +IptParser.__init__( ... ) + +The class constructor, initializes the internal lookup table. +]]-- + +function IptParser.__init__( self, ... ) + self._rules = { } + self._chain = nil + self:_parse_rules() +end + + +--[[ +IptParser.find( args ) + +Find all firewall rules that match the given criteria. Expects a table with search criteria as only argument. +If args is nil or an empty table then all rules will be returned. + +The following keys in the args table are recognized: + + - table Match rules that are located within the given table + - chain Match rules that are located within the given chain + - target Match rules with the given target + - protocol Match rules that match the given protocol, rules with protocol "all" are always matched + - source Match rules with the given source, rules with source "0.0.0.0/0" are always matched + - destination Match rules with the given destination, rules with destination "0.0.0.0/0" are always matched + - inputif Match rules with the given input interface, rules with input interface "*" (=all) are always matched + - outputif Match rules with the given output interface, rules with output interface "*" (=all) are always matched + - flags Match rules that match the given flags, current supported values are "-f" (--fragment) and "!f" (! --fragment) + - options Match rules containing all given options + +The return value is a list of tables representing the matched rules. +Each rule table contains the following fields: + + - index The index number of the rule + - table The table where the rule is located, can be one of "filter", "nat" or "mangle" + - chain The chain where the rule is located, e.g. "INPUT" or "postrouting_wan" + - target The rule target, e.g. "REJECT" or "DROP" + - protocol The matching protocols, e.g. "all" or "tcp" + - flags Special rule options ("--", "-f" or "!f") + - inputif Input interface of the rule, e.g. "eth0.0" or "*" for all interfaces + - outputif Output interface of the rule, e.g. "eth0.0" or "*" for all interfaces + - source The source ip range, e.g. "0.0.0.0/0" + - destination The destination ip range, e.g. "0.0.0.0/0" + - options A list of specific options of the rule, e.g. { "reject-with", "tcp-reset" } + - packets The number of packets matched by the rule + - bytes The number of total bytes matched by the rule + +Example: + +ip = luci.sys.iptparser.IptParser() +result = ip.find( { + target="REJECT", + protocol="tcp", + options={ "reject-with", "tcp-reset" } +} ) + +This will match all rules with target "-j REJECT", protocol "-p tcp" (or "-p all") and the option "--reject-with tcp-reset". + +]]-- + +function IptParser.find( self, args ) + + local args = args or { } + local rv = { } + + for i, rule in ipairs(self._rules) do + local match = true + + -- match table + if not ( not args.table or args.table == rule.table ) then + match = false + end + + -- match chain + if not ( match == true and ( not args.chain or args.chain == rule.chain ) ) then + match = false + end + + -- match target + if not ( match == true and ( not args.target or args.target == rule.target ) ) then + match = false + end + + -- match protocol + if not ( match == true and ( not args.protocol or rule.protocol == "all" or args.protocol == rule.protocol ) ) then + match = false + end + + -- match source (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.) + if not ( match == true and ( not args.source or rule.source == "0.0.0.0/0" or rule.source == args.source ) ) then + match = false + end + + -- match destination (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.) + if not ( match == true and ( not args.destination or rule.destination == "0.0.0.0/0" or rule.destination == args.destination ) ) then + match = false + end + + -- match input interface + if not ( match == true and ( not args.inputif or rule.inputif == "*" or args.inputif == rule.inputif ) ) then + match = false + end + + -- match output interface + if not ( match == true and ( not args.outputif or rule.outputif == "*" or args.outputif == rule.outputif ) ) then + match = false + end + + -- match flags (the "opt" column) + if not ( match == true and ( not args.flags or rule.flags == args.flags ) ) then + match = false + end + + -- match specific options + if not ( match == true and ( not args.options or self:_match_options( rule.options, args.options ) ) ) then + match = false + end + + + -- insert match + if match == true then + table.insert( rv, rule ) + end + end + + return rv +end + + +--[[ +IptParser.resync() + +Rebuild the internal lookup table, for example when rules have changed through external commands. +]]-- + +function IptParser.resync( self ) + self._rules = { } + self._chain = nil + self:_parse_rules() +end + + +--[[ +IptParser._parse_rules() + +[internal] Parse iptables output from all tables. +]]-- + +function IptParser._parse_rules( self ) + + for i, tbl in ipairs({ "filter", "nat", "mangle" }) do + + for i, rule in ipairs(luci.sys.execl("iptables -t " .. tbl .. " --line-numbers -nxvL")) do + + if rule:find( "Chain " ) == 1 then + + self._chain = rule:gsub("Chain ([^%s]*) .*", "%1") + + else + if rule:find("%d") == 1 then + + local rule_parts = luci.util.split( rule, "%s+", nil, true ) + local rule_details = { } + + rule_details["table"] = tbl + rule_details["chain"] = self._chain + rule_details["index"] = tonumber(rule_parts[1]) + rule_details["packets"] = tonumber(rule_parts[2]) + rule_details["bytes"] = tonumber(rule_parts[3]) + rule_details["target"] = rule_parts[4] + rule_details["protocol"] = rule_parts[5] + rule_details["flags"] = rule_parts[6] + rule_details["inputif"] = rule_parts[7] + rule_details["outputif"] = rule_parts[8] + rule_details["source"] = rule_parts[9] + rule_details["destination"] = rule_parts[10] + rule_details["options"] = { } + + for i = 11, #rule_parts - 1 do + rule_details["options"][i-10] = rule_parts[i] + end + + table.insert( self._rules, rule_details ) + end + end + end + end + + self._chain = nil +end + + +--[[ +IptParser._match_options( optlist1, optlist2 ) + +[internal] Return true if optlist1 contains all elements of optlist2. Return false in all other cases. +]]-- + +function IptParser._match_options( self, o1, o2 ) + + -- construct a hashtable of first options list to speed up lookups + local oh = { } + for i, opt in ipairs( o1 ) do oh[opt] = true end + + -- iterate over second options list + -- each string in o2 must be also present in o1 + -- if o2 contains a string which is not found in o1 then return false + for i, opt in ipairs( o2 ) do + if not oh[opt] then + return false + end + end + + return true +end diff --git a/libs/core/src/util.lua b/libs/core/src/util.lua new file mode 100644 index 000000000..0559fff6f --- /dev/null +++ b/libs/core/src/util.lua @@ -0,0 +1,215 @@ +--[[ +LuCI - Utility library + +Description: +Several common useful Lua functions + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.util", package.seeall) + + +-- Lua simplified Python-style OO class support emulation +function class(base) + local class = {} + + local create = function(class, ...) + local inst = {} + setmetatable(inst, {__index = class}) + + if inst.__init__ then + local stat, err = pcall(inst.__init__, inst, ...) + if not stat then + error(err) + end + end + + return inst + end + + local classmeta = {__call = create} + + if base then + classmeta.__index = base + end + + setmetatable(class, classmeta) + return class +end + + +-- Clones an object (deep on-demand) +function clone(object, deep) + local copy = {} + + for k, v in pairs(object) do + if deep and type(v) == "table" then + v = clone(v, deep) + end + copy[k] = v + end + + setmetatable(copy, getmetatable(object)) + + return copy +end + + +-- Combines two or more numerically indexed tables into one +function combine(...) + local result = {} + for i, a in ipairs(arg) do + for j, v in ipairs(a) do + table.insert(result, v) + end + end + return result +end + + +-- Checks whether a table has an object "value" in it +function contains(table, value) + for k,v in pairs(table) do + if value == v then + return true + end + end + return false +end + + +-- Dumps a table to stdout (useful for testing and debugging) +function dumptable(t, i) + i = i or 0 + for k,v in pairs(t) do + print(string.rep("\t", i) .. k, v) + if type(v) == "table" then + dumptable(v, i+1) + end + end +end + + +-- Escapes all occurences of c in s +function escape(s, c) + c = c or "\\" + return s:gsub(c, "\\" .. c) +end + + +-- Populate obj in the scope of f as key +function extfenv(f, key, obj) + local scope = getfenv(f) + scope[key] = obj +end + + +-- Checks whether an object is an instanceof class +function instanceof(object, class) + local meta = getmetatable(object) + while meta and meta.__index do + if meta.__index == class then + return true + end + meta = getmetatable(meta.__index) + end + return false +end + + +-- Creates valid XML PCDATA from a string +function pcdata(value) + value = value:gsub("&", "&") + value = value:gsub('"', """) + value = value:gsub("'", "'") + value = value:gsub("<", "<") + return value:gsub(">", ">") +end + + +-- Resets the scope of f doing a shallow copy of its scope into a new table +function resfenv(f) + setfenv(f, clone(getfenv(f))) +end + + +-- Splits a string into an array +function split(str, pat, max, regex) + pat = pat or "\n" + max = max or #str + + local t = {} + local c = 1 + + if #str == 0 then + return {""} + end + + if #pat == 0 then + return nil + end + + if max == 0 then + return str + end + + repeat + local s, e = str:find(pat, c, not regex) + table.insert(t, str:sub(c, s and s - 1)) + max = max - 1 + c = e and e + 1 or #str + 1 + until not s or max < 0 + + return t +end + +-- Removes whitespace from beginning and end of a string +function trim(str) + local s = str:gsub("^%s*(.-)%s*$", "%1") + return s +end + +-- Updates given table with new values +function update(t, updates) + for k, v in pairs(updates) do + t[k] = v + end +end + + +-- Updates the scope of f with "extscope" +function updfenv(f, extscope) + update(getfenv(f), extscope) +end + + +-- Validates a variable +function validate(value, cast_number, cast_int) + if cast_number or cast_int then + value = tonumber(value) + end + + if cast_int and value and not(value % 1 == 0) then + value = nil + end + + return value +end
\ No newline at end of file diff --git a/libs/sgi-haserl/Makefile b/libs/sgi-haserl/Makefile new file mode 100644 index 000000000..81a96f6a8 --- /dev/null +++ b/libs/sgi-haserl/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk
\ No newline at end of file diff --git a/libs/sgi-haserl/root/www/cgi-bin/index.cgi b/libs/sgi-haserl/root/www/cgi-bin/index.cgi new file mode 100755 index 000000000..aae8591c6 --- /dev/null +++ b/libs/sgi-haserl/root/www/cgi-bin/index.cgi @@ -0,0 +1,3 @@ +#!/usr/bin/haserl --shell=luac +print("Status: 302 Found") +print("Location: luci\n") diff --git a/libs/sgi-haserl/root/www/cgi-bin/luci b/libs/sgi-haserl/root/www/cgi-bin/luci new file mode 100755 index 000000000..13fec754e --- /dev/null +++ b/libs/sgi-haserl/root/www/cgi-bin/luci @@ -0,0 +1,2 @@ +#!/usr/bin/haserl --shell=luac +require("luci.dispatcher").httpdispatch()
\ No newline at end of file diff --git a/libs/sgi-haserl/root/www/cgi-bin/luci-upload b/libs/sgi-haserl/root/www/cgi-bin/luci-upload new file mode 100755 index 000000000..371967ce9 --- /dev/null +++ b/libs/sgi-haserl/root/www/cgi-bin/luci-upload @@ -0,0 +1,4 @@ +#!/usr/bin/haserl --shell=luac --upload-limit=16384 +-- This is a bit hacky: remove -upload from SCRIPT_NAME +ENV.SCRIPT_NAME = ENV.SCRIPT_NAME:sub(1, #ENV.SCRIPT_NAME - 7) +dofile("luci")
\ No newline at end of file diff --git a/libs/sgi-haserl/root/www/index.html b/libs/sgi-haserl/root/www/index.html new file mode 100644 index 000000000..39ba558df --- /dev/null +++ b/libs/sgi-haserl/root/www/index.html @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="refresh" content="0; URL=/cgi-bin/index.cgi" /> +</head> +<body style="background-color: black"> +<a style="color: white; text-decoration: none" href="/cgi-bin/index.cgi">LuCI - Lua Configuration Interface</a> +</body> +</html>
\ No newline at end of file diff --git a/libs/sgi-haserl/src/sgi/haserl.lua b/libs/sgi-haserl/src/sgi/haserl.lua new file mode 100644 index 000000000..f3994b5c8 --- /dev/null +++ b/libs/sgi-haserl/src/sgi/haserl.lua @@ -0,0 +1,97 @@ +--[[ +LuCI - SGI-Module for Haserl + +Description: +Server Gateway Interface for Haserl + +FileId: +$Id: haserl.lua 2027 2008-05-07 21:16:35Z Cyrus $ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.sgi.haserl", package.seeall) +require("luci.fs") + +-- Environment Table +luci.http.env = ENV + +-- Returns the main dispatcher URL +function luci.http.dispatcher() + return luci.http.env.SCRIPT_NAME or "" +end + +-- Returns the upload dispatcher URL +function luci.http.dispatcher_upload() + return luci.http.dispatcher() .. "-upload" +end + +-- Returns a table of all COOKIE, GET and POST Parameters +function luci.http.formvalues() + return FORM +end + +-- Gets form value from key +function luci.http.formvalue(key, default) + local c = luci.http.formvalues() + + for match in key:gmatch("[%w-_]+") do + c = c[match] + if c == nil then + return default + end + end + + return c +end + +-- Gets a table of values with a certain prefix +function luci.http.formvaluetable(prefix) + return luci.http.formvalue(prefix, {}) +end + +-- Sends a custom HTTP-Header +function luci.http.header(key, value) + print(key .. ": " .. value) +end + +-- Set Content-Type +function luci.http.prepare_content(type) + print("Content-Type: "..type.."\n") +end + +-- Asks the browser to redirect to "url" +function luci.http.redirect(url) + luci.http.status(302, "Found") + luci.http.header("Location", url) + print() +end + +-- Returns the path of an uploaded file +-- WARNING! File uploads can be easily spoofed! Do additional sanity checks! +function luci.http.upload(name) + local fpath = luci.http.formvalue(name) + local fname = luci.http.formvalue(name .. "_name") + + if fpath and fname and luci.fs.isfile(fpath) then + return fpath + end +end + +-- Sets HTTP-Status-Header +function luci.http.status(code, message) + print("Status: " .. tostring(code) .. " " .. message) +end diff --git a/libs/sgi-webuci/Makefile b/libs/sgi-webuci/Makefile new file mode 100644 index 000000000..81a96f6a8 --- /dev/null +++ b/libs/sgi-webuci/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk
\ No newline at end of file diff --git a/libs/sgi-webuci/root/lib/webuci/main.lua b/libs/sgi-webuci/root/lib/webuci/main.lua new file mode 100644 index 000000000..cb2730d14 --- /dev/null +++ b/libs/sgi-webuci/root/lib/webuci/main.lua @@ -0,0 +1,20 @@ +module("webuci", package.seeall) + +function prepare_req(uri) + require("luci.dispatcher").createindex() + env = {} + env.REQUEST_URI = uri +end + +function handle_req(context) + env.SERVER_PROTOCOL = context.server_proto + env.REMOTE_ADDR = context.remote_addr + env.REQUEST_METHOD = context.request_method + env.PATH_INFO = context.uri + env.REMOTE_PORT = context.remote_port + env.SERVER_ADDR = context.server_addr + env.SCRIPT_NAME = env.REQUEST_URI:sub(1, #env.REQUEST_URI - #env.PATH_INFO) + + luci.sgi.webuci.initenv(env) + luci.dispatcher.httpdispatch() +end
\ No newline at end of file diff --git a/libs/sgi-webuci/src/sgi/webuci.lua b/libs/sgi-webuci/src/sgi/webuci.lua new file mode 100644 index 000000000..498bca921 --- /dev/null +++ b/libs/sgi-webuci/src/sgi/webuci.lua @@ -0,0 +1,100 @@ +--[[ +LuCI - SGI-Module for Haserl + +Description: +Server Gateway Interface for Haserl + +FileId: +$Id: webuci.lua 2027 2008-05-07 21:16:35Z Cyrus $ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.sgi.webuci", package.seeall) + +local status_set = false + +-- Initialize the environment +function initenv(env) + luci.http.env = env +end + +-- Returns the main dispatcher URL +function luci.http.dispatcher() + return luci.http.env.SCRIPT_NAME or "" +end + +-- Returns the upload dispatcher URL +function luci.http.dispatcher_upload() + -- To be implemented +end + +-- Returns a table of all COOKIE, GET and POST Parameters +function luci.http.formvalues() + return webuci.vars +end + +-- Gets form value from key +function luci.http.formvalue(key, default) + return luci.http.formvalues()[key] or default +end + +-- Gets a table of values with a certain prefix +function luci.http.formvaluetable(prefix) + local vals = {} + prefix = prefix and prefix .. "." or "." + + for k, v in pairs(luci.http.formvalues()) do + if k:find(prefix, 1, true) == 1 then + vals[k:sub(#prefix + 1)] = v + end + end + + return vals +end + +-- Sends a custom HTTP-Header +function luci.http.header(key, value) + print(key .. ": " .. value) +end + +-- Set Content-Type +function luci.http.prepare_content(type) + if not status_set then + luci.http.status(200, "OK") + end + + print("Content-Type: "..type.."\n") +end + +-- Asks the browser to redirect to "url" +function luci.http.redirect(url) + luci.http.status(302, "Found") + luci.http.header("Location", url) + print() +end + +-- Returns the path of an uploaded file +-- WARNING! File uploads can be easily spoofed! Do additional sanity checks! +function luci.http.upload(name) + -- To be implemented +end + +-- Sets HTTP-Status-Header +function luci.http.status(code, message) + print(luci.http.env.SERVER_PROTOCOL .. " " .. tostring(code) .. " " .. message) + status_set = true +end diff --git a/libs/web/Makefile b/libs/web/Makefile new file mode 100644 index 000000000..f7fac7740 --- /dev/null +++ b/libs/web/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk diff --git a/libs/web/root/etc/config/luci b/libs/web/root/etc/config/luci new file mode 100644 index 000000000..87170a9b1 --- /dev/null +++ b/libs/web/root/etc/config/luci @@ -0,0 +1,39 @@ +config core main + option lang de + option mediaurlbase /luci/fledermaus + option resourcebase /luci/resources + +config core brand + option title "OpenWRT Kamikaze" + option subtitle Freifunkedition + option firmware "OpenWRT Kamikaze" + option distro "Freifunk Firmware 2.x" + +config extern flash_keep + option uci "/etc/config" + option dropbear "/etc/dropbear" + option openvpn "/etc/openvpn" + option passwd "/etc/passwd" + option ipkg "/etc/ipkg.conf" + option httpd "/etc/httpd.conf" + option firewall "/etc/firewall.user" + +config event uci_oncommit + option network "/etc/init.d/network restart" + option wireless "/etc/init.d/network restart" + option olsr "/sbin/reboot" + option dhcp "/etc/init.d/dnsmasq restart" + option luci_fw "/etc/init.d/luci_fw restart" + option dropbear "/etc/init.d/dropbear restart" + option httpd "/etc/init.d/httpd restart" + option fstab "/etc/init.d/fstab restart" + option qos "/etc/init.d/qos restart" + option luci_splash "/etc/init.d/luci_splash restart" + option freifunk "/etc/init.d/luci_freifunk restart" + +config internal languages + option de "Deutsch" + option en "English" + +config internal themes + option Fledermaus "/luci/fledermaus" diff --git a/libs/web/src/config.lua b/libs/web/src/config.lua new file mode 100644 index 000000000..854b12814 --- /dev/null +++ b/libs/web/src/config.lua @@ -0,0 +1,48 @@ +--[[ +LuCI - Configuration + +Description: +Some LuCI configuration values read from uci file "luci" + + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.config", package.seeall) +require("luci.model.uci") +require("luci.util") +require("luci.sys") + +-- Warning! This is only for fallback and compatibility purporses! -- +main = {} + +-- This is where stylesheets and images go +main.mediaurlbase = "/luci/media" + +-- Does anybody think about browser autodetect here? +-- Too bad busybox doesn't populate HTTP_ACCEPT_LANGUAGE +main.lang = "de" + + +-- Now overwrite with UCI values +local ucidata = luci.model.uci.sections("luci") +if ucidata then + luci.util.update(luci.config, ucidata) +end
\ No newline at end of file diff --git a/libs/web/src/dispatcher.lua b/libs/web/src/dispatcher.lua new file mode 100644 index 000000000..175f0dcb0 --- /dev/null +++ b/libs/web/src/dispatcher.lua @@ -0,0 +1,281 @@ +--[[ +LuCI - Dispatcher + +Description: +The request dispatcher and module dispatcher generators + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.dispatcher", package.seeall) +require("luci.http") +require("luci.sys") +require("luci.fs") + +-- Local dispatch database +local tree = {nodes={}} + +-- Index table +local index = {} + +-- Global request object +request = {} + +-- Active dispatched node +dispatched = nil + +-- Status fields +built_index = false +built_tree = false + + +-- Builds a URL +function build_url(...) + return luci.http.dispatcher() .. "/" .. table.concat(arg, "/") +end + +-- Sends a 404 error code and renders the "error404" template if available +function error404(message) + luci.http.status(404, "Not Found") + message = message or "Not Found" + + require("luci.template") + if not pcall(luci.template.render, "error404") then + luci.http.prepare_content("text/plain") + print(message) + end + return false +end + +-- Sends a 500 error code and renders the "error500" template if available +function error500(message) + luci.http.status(500, "Internal Server Error") + + require("luci.template") + if not pcall(luci.template.render, "error500", {message=message}) then + luci.http.prepare_content("text/plain") + print(message) + end + return false +end + +-- Creates a request object for dispatching +function httpdispatch() + local pathinfo = luci.http.env.PATH_INFO or "" + local c = tree + + for s in pathinfo:gmatch("([%w_]+)") do + table.insert(request, s) + end + + dispatch() +end + +-- Dispatches a request +function dispatch() + if not built_tree then + createtree() + end + + local c = tree + local track = {} + + for i, s in ipairs(request) do + c = c.nodes[s] + if not c then + break + end + + for k, v in pairs(c) do + track[k] = v + end + end + + + if track.i18n then + require("luci.i18n").loadc(track.i18n) + end + + if track.setgroup then + luci.sys.process.setgroup(track.setgroup) + end + + if track.setuser then + luci.sys.process.setuser(track.setuser) + end + + -- Init template engine + local tpl = require("luci.template") + tpl.viewns.translate = function(...) return require("luci.i18n").translate(...) end + tpl.viewns.controller = luci.http.dispatcher() + tpl.viewns.uploadctrl = luci.http.dispatcher_upload() + tpl.viewns.media = luci.config.main.mediaurlbase + tpl.viewns.resource = luci.config.main.resourcebase + + -- Load default translation + require("luci.i18n").loadc("default") + + + if c and type(c.target) == "function" then + dispatched = c + + stat, err = pcall(c.target) + if not stat then + error500(err) + end + else + error404() + end +end + +-- Generates the dispatching tree +function createindex() + index = {} + local path = luci.sys.libpath() .. "/controller/" + local suff = ".lua" + + if pcall(require, "fastindex") then + createindex_fastindex(path, suff) + else + createindex_plain(path, suff) + end + + built_index = true +end + +-- Uses fastindex to create the dispatching tree +function createindex_fastindex(path, suffix) + local fi = fastindex.new("index") + fi.add(path .. "*" .. suffix) + fi.add(path .. "*/*" .. suffix) + fi.scan() + + for k, v in pairs(fi.indexes) do + index[v[2]] = v[1] + end +end + +-- Calls the index function of all available controllers +function createindex_plain(path, suffix) + local controllers = luci.util.combine( + luci.fs.glob(path .. "*" .. suffix) or {}, + luci.fs.glob(path .. "*/*" .. suffix) or {} + ) + + for i,c in ipairs(controllers) do + c = "luci.controller." .. c:sub(#path+1, #c-#suffix):gsub("/", ".") + stat, mod = pcall(require, c) + + if stat and mod and type(mod.index) == "function" then + index[c] = mod.index + end + end +end + +-- Creates the dispatching tree from the index +function createtree() + if not built_index then + createindex() + end + + for k, v in pairs(index) do + luci.util.updfenv(v, _M) + + local stat, mod = pcall(require, k) + if stat then + luci.util.updfenv(v, mod) + end + + pcall(v) + end + + built_tree = true +end + +-- Shortcut for creating a dispatching node +function entry(path, target, title, order, add) + add = add or {} + + local c = node(path) + c.target = target + c.title = title + c.order = order + + for k,v in pairs(add) do + c[k] = v + end + + return c +end + +-- Fetch a dispatching node +function node(...) + local c = tree + + if arg[1] and type(arg[1]) == "table" then + arg = arg[1] + end + + for k,v in ipairs(arg) do + if not c.nodes[v] then + c.nodes[v] = {nodes={}} + end + + c = c.nodes[v] + end + + return c +end + +-- Subdispatchers -- +function alias(...) + local req = arg + return function() + request = req + dispatch() + end +end + +function template(name) + require("luci.template") + return function() luci.template.render(name) end +end + +function cbi(model) + require("luci.cbi") + require("luci.template") + + return function() + local stat, res = pcall(luci.cbi.load, model) + if not stat then + error500(res) + return true + end + + local stat, err = pcall(res.parse, res) + if not stat then + error500(err) + return true + end + + luci.template.render("cbi/header") + res:render() + luci.template.render("cbi/footer") + end +end diff --git a/libs/web/src/http.lua b/libs/web/src/http.lua new file mode 100644 index 000000000..fa8821c5a --- /dev/null +++ b/libs/web/src/http.lua @@ -0,0 +1,36 @@ +--[[ +LuCI - HTTP-Interaction + +Description: +HTTP-Header manipulator and form variable preprocessor + +FileId: +$Id$ + +ToDo: +- Cookie handling + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.http", package.seeall) + +if ENV and ENV.HASERLVER then + require("luci.sgi.haserl") +elseif webuci then + require("luci.sgi.webuci") +end
\ No newline at end of file diff --git a/libs/web/src/i18n.lua b/libs/web/src/i18n.lua new file mode 100644 index 000000000..3a8a9a6c7 --- /dev/null +++ b/libs/web/src/i18n.lua @@ -0,0 +1,63 @@ +--[[ +LuCI - Internationalisation + +Description: +A very minimalistic but yet effective internationalisation module + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.i18n", package.seeall) +require("luci.sys") + +table = {} +i18ndir = luci.sys.libpath() .. "/i18n/" + +-- Clears the translation table +function clear() + table = {} +end + +-- Loads a translation and copies its data into the global translation table +function load(file) + local f = loadfile(i18ndir .. file) + if f then + setfenv(f, table) + f() + return true + else + return false + end +end + +-- Same as load but autocompletes the filename with .LANG from config.lang +function loadc(file) + return load(file .. "." .. require("luci.config").main.lang) +end + +-- Returns the i18n-value defined by "key" or if there is no such: "default" +function translate(key, default) + return table[key] or default +end + +-- Translate shourtcut with sprintf/string.format inclusion +function translatef(key, default, ...) + return translate(key, default):format(...) +end
\ No newline at end of file diff --git a/libs/web/src/template.lua b/libs/web/src/template.lua new file mode 100644 index 000000000..369aa0a30 --- /dev/null +++ b/libs/web/src/template.lua @@ -0,0 +1,220 @@ +--[[ +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 <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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.template", package.seeall) + +require("luci.config") +require("luci.util") +require("luci.fs") +require("luci.http") + +viewdir = luci.sys.libpath() .. "/view/" + + +-- Compile modes: +-- none: Never compile, only use precompiled data from files +-- memory: Always compile, do not save compiled files, ignore precompiled +-- file: Compile on demand, save compiled files, update precompiled +compiler_mode = "memory" + + +-- This applies to compiler modes "always" and "smart" +-- +-- Produce compiled lua code rather than lua sourcecode +-- WARNING: Increases template size heavily!!! +-- This produces the same bytecode as luac but does not have a strip option +compiler_enable_bytecode = false + + +-- Define the namespace for template modules +viewns = { + write = io.write, + include = function(name) Template(name):render(getfenv(2)) end, +} + +-- Compiles a given template into an executable Lua module +function compile(template) + -- Search all <% %> expressions (remember: Lua table indexes begin with #1) + local function expr_add(command) + table.insert(expr, command) + return "<%" .. tostring(#expr) .. "%>" + end + + -- As "expr" should be local, we have to assign it to the "expr_add" scope + local expr = {} + luci.util.extfenv(expr_add, "expr", expr) + + -- Save all expressiosn to table "expr" + template = template:gsub("<%%(.-)%%>", expr_add) + + local function sanitize(s) + s = luci.util.escape(s) + s = luci.util.escape(s, "'") + s = luci.util.escape(s, "\n") + return s + end + + -- Escape and sanitize all the template (all non-expressions) + template = sanitize(template) + + -- Template module header/footer declaration + local header = "write('" + local footer = "')" + + template = header .. template .. footer + + -- Replacements + local r_include = "')\ninclude('%s')\nwrite('" + local r_i18n = "'..translate('%1','%2')..'" + local r_pexec = "'..(%s or '')..'" + local r_exec = "')\n%s\nwrite('" + + -- Parse the expressions + for k,v in pairs(expr) do + local p = v:sub(1, 1) + local re = nil + if p == "+" then + re = r_include:format(sanitize(string.sub(v, 2))) + elseif p == ":" then + re = sanitize(v):gsub(":(.-) (.+)", r_i18n) + elseif p == "=" then + re = r_pexec:format(v:sub(2)) + else + re = r_exec:format(v) + end + template = template:gsub("<%%"..tostring(k).."%%>", re) + end + + if compiler_enable_bytecode then + tf = loadstring(template) + template = string.dump(tf) + end + + return template +end + +-- Oldstyle render shortcut +function render(name, scope, ...) + scope = scope or getfenv(2) + local s, t = pcall(Template, name) + if not s then + error(t) + else + t:render(scope, ...) + end +end + + +-- Template class +Template = luci.util.class() + +-- Shared template cache to store templates in to avoid unnecessary reloading +Template.cache = {} + + +-- Constructor - Reads and compiles the template on-demand +function Template.__init__(self, name) + if self.cache[name] then + self.template = self.cache[name] + else + self.template = nil + end + + -- Create a new namespace for this template + self.viewns = {} + + -- Copy over from general namespace + for k, v in pairs(viewns) do + self.viewns[k] = v + end + + -- If we have a cached template, skip compiling and loading + if self.template then + return + end + + -- Compile and build + local sourcefile = viewdir .. name .. ".htm" + local compiledfile = viewdir .. name .. ".lua" + local err + + if compiler_mode == "file" then + local tplmt = luci.fs.mtime(sourcefile) + local commt = luci.fs.mtime(compiledfile) + + -- Build if there is no compiled file or if compiled file is outdated + if ((commt == nil) and not (tplmt == nil)) + or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then + local source + source, err = luci.fs.readfile(sourcefile) + + if source then + local compiled = compile(source) + luci.fs.writefile(compiledfile, compiled) + self.template, err = loadstring(compiled) + end + else + self.template, err = loadfile(compiledfile) + end + + elseif compiler_mode == "none" then + self.template, err = loadfile(self.compiledfile) + + elseif compiler_mode == "memory" then + local source + source, err = luci.fs.readfile(sourcefile) + if source then + self.template, err = loadstring(compile(source)) + end + + end + + -- If we have no valid template throw error, otherwise cache the template + if not self.template then + error(err) + else + self.cache[name] = self.template + end +end + + +-- Renders a template +function Template.render(self, scope) + scope = scope or getfenv(2) + + -- Save old environment + local oldfenv = getfenv(self.template) + + -- Put our predefined objects in the scope of the template + luci.util.resfenv(self.template) + luci.util.updfenv(self.template, scope) + luci.util.updfenv(self.template, self.viewns) + + -- Now finally render the thing + self.template() + + -- Reset environment + setfenv(self.template, oldfenv) +end |