diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile | 47 | ||||
-rw-r--r-- | lib/README | 3 | ||||
-rw-r--r-- | lib/ed.lua | 60 | ||||
-rw-r--r-- | lib/fs.lua | 89 | ||||
-rw-r--r-- | lib/html.lua | 245 | ||||
-rw-r--r-- | lib/join.lua | 25 | ||||
-rw-r--r-- | lib/log_view.lua | 56 | ||||
-rw-r--r-- | lib/menubuilder.lua | 150 | ||||
-rw-r--r-- | lib/service_controller.lua | 189 | ||||
-rw-r--r-- | lib/service_model.lua | 115 | ||||
-rw-r--r-- | lib/session.lua | 143 | ||||
-rw-r--r-- | lib/split.lua | 31 | ||||
-rwxr-xr-x | lib/validator.lua | 156 | ||||
-rw-r--r-- | lib/web_elements.lua | 156 |
14 files changed, 1465 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..0c65ca1 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,47 @@ +include ../config.mk + +LIB_DIST=ed.lua\ + fs.lua\ + html.lua\ + join.lua\ + log_view.lua\ + menubuilder.lua\ + service_controller.lua\ + service_model.lua\ + session.lua\ + split.lua\ + validator.lua\ + web_elements.lua + +EXTRA_DIST=README Makefile +DISTFILES=$(LIB_DIST) $(EXTRA_DIST) + +install_dir=$(DESTDIR)/$(acflibdir) +dist_dir=$(DISTDIR)/$(notdir $(PWD)) + +phony+=all +all: + +phony+=clean +clean: + +phony+=distdir +distdir: $(DISTFILES) + mkdir -p "$(dist_dir)" + for i in $(DISTFILES); do\ + dest=`dirname "$(dist_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + +phony+=install +install: + mkdir -p $(install_dir) + for i in $(LIB_DIST); do\ + dest=`dirname "$(install_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + + +.PHONY: $(phony) diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..cbc9798 --- /dev/null +++ b/lib/README @@ -0,0 +1,3 @@ +In the process of cleaning up... +shell libs are no longer used, just keeping them for historical reference at this point +lua libs are being worked on. diff --git a/lib/ed.lua b/lib/ed.lua new file mode 100644 index 0000000..2e86f08 --- /dev/null +++ b/lib/ed.lua @@ -0,0 +1,60 @@ +#!/usr/bin/lua + +require "object" + +-- ed object +Ed = Object:new{ + filename = nil, + lines = {} +} + + +-- openfile and read it to table +function Ed:open( filename, mode ) + local f = io.open( filename, mode ) + -- check that open was success + if f == nil then + return nil + end + + -- read the lines + for line in f:lines() do + table.insert( self.lines, line ) + end + f:close() + self.filename = filename + return self.lines +end + + +-- search and replace on lines that matches linematch +function Ed:find_gsub( linematch, search, replace, limit ) + local i, line + for i, line in ipairs( self.lines ) do + if string.find( line, linematch ) then + self.lines[i] = string.gsub( line, search, replace, limit ) + end + end +end + + +-- Write the table to file again +function Ed:flush( filename ) + local f = io.open( filename, "w" ) + if f == nil then + return false + end + + -- write each line to file + for i, line in ipairs( self.lines ) do + f:write(line .. "\n") -- test this! + end + f:close() + return true +end + +function Ed:insert( line ) + self.lines:insert( line ) +end + + diff --git a/lib/fs.lua b/lib/fs.lua new file mode 100644 index 0000000..d171074 --- /dev/null +++ b/lib/fs.lua @@ -0,0 +1,89 @@ +--[[ + module for generic filesystem funcs + + Copyright (c) Natanael Copa 2006 + +]]-- + +module (..., package.seeall) + +require ("lfs") + +-- generic wrapper funcs +function is_dir ( pathstr ) + return lfs.attributes ( pathstr, "mode" ) == "directory" +end + +function is_file ( pathstr ) + return lfs.attributes ( pathstr, "mode" ) == "file" +end + +-- Returns the contents of a file as a string +function read_file ( path ) + local file = io.open(path) + if ( file ) then + local f = file:read("*a") + file:close() + return f + else + return nil + end +end + +-- Returns an array with the contents of a file, +-- or nil and the error message +function read_file_as_array ( path ) + local file, error = io.open(path) + if ( file == nil ) then + return nil, error + end + local f = {} + for line in file:lines() do + table.insert ( f , line ) + end + file:close() + return f +end + + + + +-- write a string to a file +function write_file ( path, str ) + local file = io.open(path, "w") + if ( file ) then + file:write(str) + file:close() + end +end + + +-- iterator function for finding dir entries matching filespec (what) +-- starting at where, or currentdir if not specified. +-- Finds regexes, not fileglobs +function find ( what, where ) + -- returns an array of files under "where" that match what "f" + local function find_files_as_array ( f, where, t ) + where = where or lfs.currentdir() + f = f or ".*" + t = t or {} + for d in lfs.dir ( where ) do + if fs.is_dir ( where .. "/" .. d ) and (d ~= ".") and ( d ~= "..") then + find_files_as_array (f, where .. "/" .. d, t ) + end + if (string.match (d, "^" .. f .. "$" )) then + table.insert (t, ( string.gsub ( where .. "/" .. d, "/+", "/" ) ) ) + end + end + return (t) + end + + -- This is the iterator + local t = find_files_as_array ( what, where ) + local idx = 0 + return function () + idx = idx + 1 + return t[idx] + end +end + diff --git a/lib/html.lua b/lib/html.lua new file mode 100644 index 0000000..7e60c2c --- /dev/null +++ b/lib/html.lua @@ -0,0 +1,245 @@ +--[[ lowlevel html functions + Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org + Copyright (C) 2007 Nathan Angelacos + Licensed under the terms of GPL2 +]]-- +module (..., package.seeall) + +--[[ Cookie functions ]]------------------------------------------------------ +cookie={} + +-- Set a cookie - returns a string suitable for setting a cookie +-- if the value is the boolean "false", then set the cookie to expire +cookie.set = function ( name, value, path ) + local expires = "" + if name == nil then + return ("") + end + if value == false then + expires = 'expires=Thu Jan 1 00:00:00 EST 1970' + value = "" + end + if path == nil then + path = "/" + end + return (string.format('Set-Cookie: %s=%s; path=%s; %s\n', tostring(name), + tostring(value), path, expires)) +end + + +-- wrapper function to clear a cookie +cookie.unset = function ( name, path) + return cookie.set (name, false, path) +end + + + +-- escape unsafe html characters +function html_escape (text ) + text = text or "" + local str = string.gsub (text, "&", "&" ) + str = string.gsub (str, "<", "<" ) + return string.gsub (str, ">", ">" ) +end + +-- return a name,value pair as a string. +nv_pair = function ( name, value) + if ( name == nil ) then + return ( value or "" ) + end + + if ( type(value) == "boolean" ) then + value = tostring(value) + end + + if ( value == nil ) then + return ( "" ) + else + return (string.format (' %s="%s" ', name , ( value or "" ) )) + end +end + + +--[[ + each of these functions take a table that has an associative array of + the values we might care about: + + value -- this is the value in the form element, or the selected element + name -- this is the name of the element + cols, rows + class + id + etc. +]]-- + +local generic_input = function ( field_type, v ) + if type(v.value) == "table" then + ret = {} + local vals = v.value + for n, val in ipairs(vals) do + v.value = val + table.insert(ret, generic_input(field_type, v)) + end + v.value = vals + return table.concat(ret) + end + if ( field_type == nil ) then + return nil + end + + local str = string.format ( '<input type="%s" ', field_type ) + + for i,k in ipairs ( { + "name", "size", "checked", "maxlength", + "value", "length", "class", "id", "src", + "align", "alt", + "tabindex", "accesskey", "onfocus", "onblur" + } ) do + str = str .. nv_pair ( k, v[k] ) + end + + if ( v.disabled ~= nil ) then + str = str .. " disabled" + end + + return ( str .. ">" ) +end + + +--[[ Form functions ]]------------------------------------------------------ +-- These expect something like a cfe to work (see acf_www-controller.lua) + +form = {} +form.text = function ( v ) + return generic_input ( "text", v ) +end + + +form.longtext = function ( v ) + local str = "<textarea" + for i,k in ipairs ( { + "name", "rows", "cols", + "class", "id", "tabindex", "accesskey", + "onfocus", "onblur" + } ) do + str = str .. nv_pair ( k, v[k] ) + end + str = str .. nv_pair (nil, v.disabled) + return ( str .. ">" .. (v.value or "" ) .. "</textarea>" ) +end + + +function form.passwd ( v ) + return generic_input ( "password", v ) +end + +function form.hidden ( v ) + return generic_input ( "hidden", v ) +end + + +function form.submit ( v ) + return generic_input ( "submit", v ) +end + + +function form.action (v) + return generic_input ("submit", v) +end + +function form.file ( v ) + return generic_input ( "file", v ) +end + +function form.image ( v ) + return generic_input ( "image", v ) +end + + +-- v.value is the selected item +-- v.option is an array of valid options +-- NOTE use of value and values (plural) +function form.select ( v ) + if ( v.name == nil ) then + return nil + end + local str = "<select" + for i,k in ipairs ( { + "name", "size", "tabindex", "accesskey", + "onfocus", "onblur", "onchange", "id", + "class" + } ) do + str = str .. nv_pair ( k, v[k] ) + end + + if ( v.disabled ~= nil ) then + str = str .. " disabled" + end + str = str .. ">" + -- now the options + for i, k in ipairs ( v.option ) do + local val = k + local txt = nil + if type(val) == "table" then + txt=val[1] + val=val[0] + end + str = str .. "<option " + if ( v.value == val ) then + str = str .. " selected " + end + str = str .. nv_pair("value", val) .. ">" .. k .. "</option>" + end + str = str .. "</select>" + return (str) +end + +function form.checkbox ( v ) + return generic_input ( "checkbox", v ) +end + + +-- NOTE: VALUE of a form is a table containing the form elements ... +function form.start ( v) + if ( v.action == nil ) then + return nil + end + + local method = v.method or "get" + return ( string.format ( + '<form %s%s%s>', + nv_pair ( "class", v.class ), + nv_pair ( "method", v.method), + nv_pair ( "action", v.action ) + ) ) +end + +function form.stop ( ) + return ("</form>") +end + +-- For "h1, h2, p," etc +-- WARNING - Text is printed verbatim - you may want to +-- wrap the text in html_escape +function entity (tag, text, class, id) + return ( string.format ( + "<%s%s%s>%s</%s>", + tag, + nv_pair ("class", class), + nv_pair("id", id), text , tag) + ) +end + + +function link ( v ) + if ( v.value == nil ) then + return nil + end + local str = nv_pair ( "href", v.value ) + for i,k in ipairs( { "class", "id" }) do + str = str .. nv_pair ( k, v[k] ) + end + + return ( "<a " .. str .. ">" .. (v.label or "" ) .. "</a>" ) +end + diff --git a/lib/join.lua b/lib/join.lua new file mode 100644 index 0000000..e3621de --- /dev/null +++ b/lib/join.lua @@ -0,0 +1,25 @@ +--[[ + module for joining an array to a string +]]-- + +module (..., package.seeall) + + +-- This code comes from http://lua-users.org/wiki/SplitJoin +-- +-- Concat the contents of the parameter list, +-- -- separated by the string delimiter (just like in perl) +-- -- example: strjoin(", ", {"Anna", "Bob", "Charlie", "Dolores"}) +return function (delimiter, list) + local len = getn(list) + if len == 0 then + return "" + end + local string = list[1] + for i = 2, len do + string = string .. delimiter .. list[i] + end + return string +end + + diff --git a/lib/log_view.lua b/lib/log_view.lua new file mode 100644 index 0000000..f32400e --- /dev/null +++ b/lib/log_view.lua @@ -0,0 +1,56 @@ +require ("web_elements") + +local function fwrite(fmt, ...) + return io.write(string.format(fmt, ...)) +end + +local function footer(time) + fwrite("<div id=\"footer\">\n<p>This request was processed in approximately %d seconds</p>\n</div>",time) +end + +header = [[ +content-type: text/html + +<!DOCTYPE HTML PUBLIC "-//W3C//ddD HTML 4.01 Transitional//EN"> +<html lang="en"> + +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /> +<title>Alpine log view</title> +<link rel="stylesheet" type="text/css" href="/static/webconf.css" /> +<meta http-equiv='Cache-Control' content='no-cache' /> +<Meta http-equiv='Pragma' content='no-cache' /> +</head>]] + +-- + +print(header) +print("<body>\n<div id=\"head\">") + +fwrite("<h1>%s</h1>",cf.hostinfo.alpine_hostname) + +fwrite("<p><em>%s</em></p>",cf.hostinfo.alpine_release) + +print("</div>") + +print ('<div id="mainmenu">') +local group, cat, subcat = + web_elements.render_mainmenu ( menu, cf.prefix, cf.controller, cf.action ) +print([[ +</div> + +<div id="submenu">]]) +web_elements.render_submenu ( menu, group, cat, subcat ) +print([[</div> + +<div id="content"> +<p>]]) +-- get the wc and view tables +-- walk the tree +web_elements.render_table ( view ) +print("</p>\n</div>") + +print(footer(cf.time)) +print("</body></html>") + +-- /* vim: set filetype=lua : */ diff --git a/lib/menubuilder.lua b/lib/menubuilder.lua new file mode 100644 index 0000000..6def1fe --- /dev/null +++ b/lib/menubuilder.lua @@ -0,0 +1,150 @@ +--[[ parse through the *.menu tables and return a "menu" table + Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org + Copyright (C) 2007 Nathan Angelacos + Licensed under the terms of GPL2 + ]]-- +module(..., package.seeall) + +-- returns a table of the "*.menu" tables +-- uses the system "find" command +-- startdir should be the app dir. +local get_candidates = function (startdir) + local t = {} + startdir = startdir .. "/" + local fh = io.popen('find ' .. startdir .. ' -name "*.menu"') + + local start = string.gsub(startdir, "/$", "") + for x in fh:lines() do + table.insert (t, (string.gsub(x, start, ""))) + end + + return t +end + + +-- internal function for table.sort +local t_compare = function (x,y,f) + for k,v in pairs(f) do + local a = x[v] + local b = y[v] + if tonumber(a) and tonumber(b) then + a=tonumber(a) + b=tonumber(b) + end + if a < b then return true end + if a > b then return false end + end + return false + end + + +-- returns a table of all the menu items found, sorted by priority +-- Table format: prefix controller cat group tab action +get_menuitems = function (startdir) + local t = {} + for k,v in pairs(get_candidates(startdir)) do + local prefix, controller = mvc.dirname(v), mvc.basename(v, ".menu") + -- open the thing, and parse the contents + local fh = io.open(startdir .. "/" .. v) + local prio = 10 + for x in fh:lines() do + if not string.match(x, "^#") then + local item = {} + for i in string.gmatch(x, "%S+") do + table.insert(item, i) + end + table.insert(t, { prefix=prefix, + controller=controller, + catprio="nan", + cat=item[1] or "", + groupprio="nan", + group=item[2] or "", + tabprio=tostring(prio), + tab=item[3] or "", + action=item[4] or "" }) + prio=prio+5 + end + end + fh:close() + end + -- Ok, we now have the raw menu table + -- now try to parse out numbers in front of any cat, group or tabs + for x in ipairs(t) do + local f = t[x] + if (string.match(f.cat, "^%d")) then + f.catprio, f.cat = string.match(f.cat, "(%d+)(.*)") + end + if (string.match(f.group, "^%d")) then + f.groupprio, f.group = string.match(f.group, "(%d+)(.*)") + end + if (string.match(f.tab, "^%d")) then + f.tabprio, f.tab = string.match(f.tab, "(%d+)(.*)") + end + end + + -- Convert underscores to spaces + for x in ipairs(t) do + t[x].cat = string.gsub(t[x].cat, "_", " ") + t[x].group = string.gsub(t[x].group, "_", " ") + t[x].tab = string.gsub(t[x].tab, "_", " ") + end + + -- Now alpha sort + table.sort(t, function(x,y) + return t_compare (x,y,{"cat", "catprio", "group", "groupprio", "tab", "tabprio"} ) + end) + + -- Fill in the priorities + local fill_prio = function (t, start, stop, col) + local prio = t[start][col] + if prio == "nan" then prio = "0" end + while start <= stop do + t[start][col] = prio + start = start + 1 + end + end + + +-- Fill in the priorities +-- Warning - UGLY code ahead. +-- Basic rules, for each cat and group, if the prio is nan, then set it +-- to the lowest value for that group or cat. + local k = 1 + while ( k <= table.maxn(t) ) do + local c = k + while ( c <= table.maxn(t) and t[c].cat == t[k].cat ) do + c=c+1 + end + c=c-1 -- back up one - we only want whats the same + fill_prio(t,k,c,"catprio") + -- from k,c is a mini table, do the same for groupprio + local g = k + while ( g <= c ) do + local h = g + while ( h <= c and t[h].group == t[g].group ) do + h=h+1 + end + h=h-1 --- back up one (again) + fill_prio(t,g,h,"groupprio") + g=h+1 + end + k = c + 1 + end + + -- Now priority sort + table.sort(t, function(x,y) + return t_compare (x,y,{"catprio", "cat", "groupprio", "group", "tabprio", "tab"} ) + end) + + + -- drop the priorities - they were internal + for k,v in ipairs(t) do + v.catprio = nil + v.groupprio = nil + v.tabprio = nil + end + + return t +end + + diff --git a/lib/service_controller.lua b/lib/service_controller.lua new file mode 100644 index 0000000..09595a4 --- /dev/null +++ b/lib/service_controller.lua @@ -0,0 +1,189 @@ +local function build_status(model, name) + return { + type = "form", + method = "post", + action = cf.uri .. "/chrun", + value = { + { + type = "formtext", + name = "status", + label = "Status: <B>" .. name .. "</B>", + value = model.status(name) + }, + { + type = "hidden", + name = "service", + value = name + }, + { + type = "submit", + name = "cmd", + label = "Running controls", + value = { "Start", "Stop", "Restart" } + }, + } + } +end + +local function build_note(model, name) + return { + type = "form", + method = "post", + action = cf.uri .. "/note_set", + value = { + { + type = "textarea", + name = "note", + label = "Notes", + cols = 80, + rows = 8, + value = model.get_note(name) + }, + { + type = "submit", + label = "Command", + value = "Save" + } + } + } +end + +local function mkerr(str) + return { + { + type = "formtext", + class = "error", + value = str + } + } +end + +local function mkresult(str) + return { + { + type = "formtext", + value = str + } + } +end + +local function chrun(model, name, ucmd) + local cmd = string.lower(ucmd) + --TODO chect that name is in get_service_names() + if cmd ~= "start" and cmd ~= "stop" and cmd ~= "restart" then + return mkerr("unknown command") + end + return mkresult(model.initd(name, cmd)) +end + +local function build_log_file(model, name) + return { + type = "group", + label = name, + text = "FILE " .. model.get_log_names()[name].path, + value = { + { + type = "log", + lines = model.get_log_producer(name) + } + } + } +end + +local function build_edit_file(model, name) + return { + type = "form", + method = "post", + action = cf.uri .. "/cfg_set", + value = { + { + type = "textarea", + name = "cfg", + label = name, + cols = 80, + rows = 12, + value = model.get_cfg(name) + }, + { + type = "hidden", + name = "name", + value = name + }, + { + type = "submit", + label = "Command", + value = "Save" + } + } + } + +end + +local function build_cfg(model) + local ret = {} + for name, whatever in pairs(model.get_cfg_names()) do + table.insert(ret, build_edit_file(model, name)) + end + return ret +end + +local function set_cfg(model) + for name, whatever in pairs(model.get_cfg_names()) do + if name == FORM.name then + model.set_cfg(name, FORM.cfg) + return + end + end + return mkerr("unknown config") +end + +local function build_log(model) + local ret = {} + for name, whatever in pairs(model.get_log_names()) do + table.insert(ret, build_log_file(model, name)) + end + return ret +end + +local function build_service(model) + local ret = {} + for name, whatever in pairs(model.get_service_names()) do + table.insert(ret, build_status(model, name)) + end + table.insert(ret, build_note(model)) + return ret +end + +function create_service_controller() + + local me = {} + + me.service = build_service + me.default = me.service + me.cfg = build_cfg + me.log = build_log + + function me.chrun(model) + return chrun(model, FORM.service, FORM.cmd) + end + + function me.note_set(model) + local ret = model.set_note(FORM.note) + if ret then + return ret + end + return me.service(model) + end + + + function me.cfg_set(model) + set_cfg(model) + return me.cfg(model) + end + + return me + +end + +-- /* vim: set filetype=lua shiftwidth=4: */ + diff --git a/lib/service_model.lua b/lib/service_model.lua new file mode 100644 index 0000000..c619d61 --- /dev/null +++ b/lib/service_model.lua @@ -0,0 +1,115 @@ +require("fs") + +function create_service_model(cfglist, loglist, servlist, notepath) + + local me = {} + + local function get_any_pid(pname) + for e in lfs.dir("/proc") do + if e == string.match(e, "^%d*$") then + for line in io.lines("/proc/" .. e .. "/status") do + tag, val = string.match(line, "^([^:]*):%s*(%S*)$"); + if tag == "Name" then + if val == pname then return e end + break + end + end + end + end + end + + local function get_cfg_path(name) + if not cfglist or not cfglist[name] then + return + end + return cfglist[name].path + end + + local function get_log_path(name) + if not loglist or not loglist[name] then + return + end + return loglist[name].path + end + + --[[ Public Functions go here ]]-- + + function me.status(name) + if not servlist or not servlist[name] then + return "unknown service" + end + local cmdname = servlist[name].cmdname + if get_any_pid(cmdname) == nil then + return "seems to be stopped" + else + return "seems to be running" + end + end + + function me.initd(name, action) + if not servlist or not servlist[name] then + return "unknown service" + end + local p = io.popen("/etc/init.d/" + .. servlist[name].initdname .. " " .. action, "r") + local ret = p:read("*a") + p:close() + return ret + end + + function me.get_note() + if not notepath or not fs.is_file(notepath) then return "" end + return fs.read_file(notepath) + end + + function me.set_note(value) + if notepath == nil then return end + fs.write_file(notepath, value) + end + + function me.get_cfg_names() + return cfglist + end + + function me.get_log_names() + return loglist + end + + function me.get_service_names() + return servlist + end + + function me.get_cfg(name) + local path = get_cfg_path(name) + if not path or not fs.is_file(path) then + return "" + end + return fs.read_file(path) + end + + function me.set_cfg(name, value) + local path = get_cfg_path(name) + if path then + fs.write_file(path, value) + end + end + +-- local function wrap_single(x) +-- return function(state, prev) +-- if not prev then +-- return x +-- end +-- end +-- end + + function me.get_log_producer(name) + local path = get_log_path(name) + if not path or not fs.is_file(path) then + return "cannot access " .. path + end + return io.lines(path) + end + + return me + +end diff --git a/lib/session.lua b/lib/session.lua new file mode 100644 index 0000000..f23f5a5 --- /dev/null +++ b/lib/session.lua @@ -0,0 +1,143 @@ +-- Session handling routines - written for acf +-- Copyright (C) 2007 N. Angelacos - GPL2 License + +module (..., package.seeall) + +local b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" + +-- Return a sessionid of at least size bits length +random_hash = function (size) + local file = io.open("/dev/urandom") + local str = "" + if file == nil then return nil end + while (size > 0 ) do + local offset = (string.byte(file:read(1)) % 64) + 1 + str = str .. string.sub (b64, offset, offset) + size = size - 6 + end + return str +end + +hash_ip_addr = function (string) + local str = "" + for i in string.gmatch(string, "%d+") do + str = str .. string.format("%02x", i ) + end + return str +end + +ip_addr_from_hash = function (string) + local str = "" + for i in string.gmatch(string, "..") do + str = str .. string.format("%d", "0x" .. i) .. "." + end + return string.sub(str, 1, string.len(str)-1) +end + + +--[[ + These functions serialize a table, including nested tables. + The code based on code in PiL 2nd edition p113 +]]-- +local function basicSerialize (o) + if type(o) == "number" then + return tostring(o) + else + return string.format("%q", o) + end +end + + +function serialize (name, value, saved ) + local str = str or "" + saved = saved or {} + str = str .. name .. " = " + if type(value) == "number" or type(value) == "string" then + str = str .. basicSerialize (value) .. "\n" + elseif type(value) == "table" then + if saved[value] then + str = str .. saved[value] .. "\n" + else + saved[value] = name + str = str .. "{}\n" + for k,v in pairs(value) do + local fieldname = string.format("%s[%s]", name, basicSerialize(k)) + str = str .. serialize (fieldname, v, saved) + end + end + elseif type(value) == "boolean" then + str = str .. tostring(value) .. "\n" + else + str = str .. "nil\n" -- cannot save other types, so skip them + end + return str +end + +save_session = function( sessionpath, session, sessiontable) + local file = io.open(sessionpath .. "/" .. session , "w") + if file then + file:write ( "-- This is an ACF session table.\nlocal timestamp=" .. os.time() ) + file:write ( "\nlocal " ) + file:write ( serialize("s", sessiontable) ) + file:write ( "return timestamp, s\n") + file:close() + return true + else + return false + end +end + + +--- FIXME: This is really a generic "test if file exists" thing. + +-- Tests if a session is valid +-- Returns true if valid, false if not +session_is_valid = function (session) + local file = io.open(session) + if file then + file:close() + return true + else + return false + end +end + +-- Loads a session +-- Returns a timestamp (when the session data was saved) and the session table. +load_session = function ( sessionpath, session ) + -- session can only have b64 characters in it + session = string.gsub ( session, "[^" .. b64 .. "]", "") + if #session == 0 then + return nil, {} + end + session = sessionpath .. "/" .. session + if (session_is_valid(session)) then + local file = io.open(session) + return dofile(session) + else + return nil, {} + end +end + +-- unlinks a session +invalidate_session = function (sessionpath, session) + if type(session) ~= "string" then return nil end + local s = string.gsub (session, "[^" .. b64 .. "]", "") + if s ~= session then + return nil + end + session = sessionpath .. "/" .. s + os.remove (session) + return nil +end + + +expire_old_sessions = function ( sessiondir ) + file = io.popen("ls " .. sessiondir ) + x = file:read("*a") + file:close() + for a in string.gmatch(x, "[^%c]+") do + timestamp, foo = load_session ( sessiondir .. "/" .. a ) + print ( a .. " is " .. os.time() - timestamp .. " seconds old") + end +end diff --git a/lib/split.lua b/lib/split.lua new file mode 100644 index 0000000..f3e2e95 --- /dev/null +++ b/lib/split.lua @@ -0,0 +1,31 @@ +--[[ + module for splitting a string to an array +]]-- + +module (..., package.seeall) + +-- This code comes from http://lua-users.org/wiki/SplitJoin + +-- Split text into a list consisting of the strings in text, +-- separated by strings matching delimiter (which may be a pattern). +-- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores") +return function (delimiter, text) + local list = {} + local pos = 1 + -- this would result in endless loops + if string.find("", delimiter, 1) then + error("delimiter matches empty string!") + end + while 1 do + local first, last = string.find(text, delimiter, pos) + if first then -- found? + table.insert(list, string.sub(text, pos, first-1)) + pos = last+1 + else + table.insert(list, string.sub(text, pos)) + break + end + end + return list +end + diff --git a/lib/validator.lua b/lib/validator.lua new file mode 100755 index 0000000..951d716 --- /dev/null +++ b/lib/validator.lua @@ -0,0 +1,156 @@ +-------------------------------------------------- +-- Validation Functions for Alpine Linux' Webconf +-------------------------------------------------- + +-- setup the 'language' table +lang = {} +lang.English = 1 +lang.German = 2 +lang.French = 3 +lang.Current = lang.English + +-- setup the 'validator' tables +validator = {} +validator.msg = {} +validator.msg.err = {} + +-- setup error messages +validator.msg.err.Success = {} +validator.msg.err.Success[lang.English] = "Ok." +validator.msg.err.Success[lang.German] = "Ok." +validator.msg.err.InvalidChars = {} +validator.msg.err.InvalidChars[lang.English] = "Invalid characters!" +validator.msg.err.InvalidChars[lang.German] = "Ungültige Zeichen!" +validator.msg.err.InvalidLength = {} +validator.msg.err.InvalidLength[lang.English] = "Invalid length!" +validator.msg.err.InvalidLength[lang.German] = "Ungültige Länge!" +validator.msg.err.InvalidFormat = {} +validator.msg.err.InvalidFormat[lang.English] = "Invalid format!" +validator.msg.err.InvalidFormat[lang.German] = "Ungültiges Format!" +validator.msg.err.InvalidValue = {} +validator.msg.err.InvalidValue[lang.English] = "Invalid Value!" +validator.msg.err.InvalidValue[lang.German] = "Ungültiger Wert!" +validator.msg.err.OutOfRange = {} +validator.msg.err.OutOfRange[lang.English] = "Value out of range!" +validator.msg.err.OutOfRange[lang.German] = "Wert ausserhalb des Bereichs!" + +-- +-- This function validates an ipv4 address. +-- On success it returns 1 otherwise a negative value +-- +function validator.is_ipv4(ipv4) + local retval = false; + local nums = { "", "", "", ""}; + local iplen = string.len(ipv4); + + -- check the ipv4's length + if (iplen < 7 or iplen > 15) then + return false, validator.msg.err.InvalidLength[lang.Current] + end + + -- NC: Split the string into an array. separate with '.' (dots) + -- ^ beginning of string + -- () capture + -- \. litteral '.' The \ neutralizes the . character class. + -- %d+ one or more digits + -- $ end of string + nums = { ipv4:match ("^(%d+)\.(%d+)\.(%d+)\.(%d+)$" ) } + + -- check if all nums are filled + if ( nums[1] == nil or + nums[2] == nil or + nums[3] == nil or + nums[4] == nil) then + -- we have an empty number + return false, validator.msg.err.InvalidFormat[lang.Current] + end + + -- too big? + if (tonumber(nums[1]) > 255 or + tonumber(nums[2]) > 255 or + tonumber(nums[3]) > 255 or + tonumber(nums[4]) > 255) then + -- at least one number is too big + return false, validator.msg.err.InvalidValue[lang.Current] + end + + return true, validator.msg.err.Success[lang.Current] +end + +function validator.is_mac(mac) + + local tmpmac = string.upper(mac) + + if (string.len(tmpmac) ~= 17) then + return false, validator.msg.err.InvalidLength[lang.Current] + end + + -- check for valid characters + local step = 1; + while (step <= 17) do + if (string.sub(tmpmac, step, step) ~= ":") and + (string.sub(tmpmac, step, step) < "0" or string.sub(tmpmac, step, step) > "9") and + (string.sub(tmpmac, step, step) < "A" or string.sub(tmpmac, step, step) > "F") then + -- we have found an invalid character! + return false, validator.msg.err.InvalidChars[lang.Current] + end + step = step + 1; + end + + -- check for valid colon positions + if (string.sub(tmpmac, 3, 3) ~= ":" or + string.sub(tmpmac, 6, 6) ~= ":" or + string.sub(tmpmac, 9, 9) ~= ":" or + string.sub(tmpmac, 12, 12) ~= ":" or + string.sub(tmpmac, 15, 15) ~= ":") then + return false, validator.msg.err.InvalidFormat[lang.Current] + end + + -- check for valid non colon positions + step = 1; + while (step <= 17) do + if ((string.sub(tmpmac, step, step) == ":") and + ((step ~= 3) and (step ~= 6) and (step ~= 9) and (step ~= 12) and + (step ~= 15))) then + return false, validator.msg.err.InvalidValue[lang.Current] + end + step = step + 1; + end + + return true, validator.msg.err.Success[lang.Current] +end + +-- +-- This function checks if the given input +-- consists of number-chars between 0..9 only +-- and eventually a leading '-' +-- +function validator.is_integer(numstr) + -- ^ beginning of string + -- -? one or zero ot the char '-' + -- %d+ one or more digits + -- $ end of string + return string.find(numstr, "^-?%d+$") ~= nil +end + + +-- +-- This function checks if the given input +-- consists of number-chars between 0..9 only +-- and if it is within a given range. +-- +function validator.is_integer_in_range(numstr, min, max) + return validator.is_integer(numstr) + and numstr >= min + and numstr <= max + +end + +-- +-- This function checks if the given number is an integer +-- and wheter it is between 1 .. 65535 +-- +function validator.is_port(numstr) + return validator.is_integer_in_range(numstr, 1, 65535) +end + diff --git a/lib/web_elements.lua b/lib/web_elements.lua new file mode 100644 index 0000000..e676205 --- /dev/null +++ b/lib/web_elements.lua @@ -0,0 +1,156 @@ +--[[ + middle level functions for the webconf view templates. Part of + acf + + Copyright (C) 2006 N. Angelacos Licensed under terms of GPL2 +]]-- + +module ( ..., package.seeall ) + +require ("html") + +-- This is the main function that walks a table formatted for a template +-- (This is the magic in generic) +function render_table ( element, level) + local level = level or 1 + + if (type(element) ~= "table" ) then + return nil + end + + for k,v in pairs (element) do + if ( v.type ~= nil ) then + if ( v.type == "group" ) then + print ( html.entity ( ( "h" .. tostring(level) ), v.label, v.class, v.id ) ) + print ( html.entity ( "p" , v.text, v.class ) ) + render_table ( v.value, level + 1 ) + elseif ( v.type == "label" ) then + print ( html.entity ( "h" .. level , v.value, v.class, v.id ) ) + if ( v.text ~= nil ) then + print ( html.entity ( "p", v.text, v.class )) + end + elseif ( v.type == "html" ) then + print (v.value) + elseif ( v.type == "log" ) then + print("<pre>") + if type(v.lines) == "function" then + for line in v.lines do + print(line) + end + elseif v.lines then + print(v.lines) + end + print("</pre>") + elseif ( v.type == "link" ) then + print (html.link ( v ) ) + elseif ( v.type == "form" ) then + print ( html.form.start ( v ) ) + print ("<dl>") + render_table ( v.value, level + 1 ) + print ("</dl>") + print ( html.form.stop () ) + elseif type(html.form[v.type]) ~= "nil" then + if v.type == "hidden" then + v.noitem = true + end + if not v.noitem then + print ( string.format ("<dt>%s</dt><dd>", ( v.label or "" ))) + end + print ( html.form[v.type] ( v ) ) + if not v.noitem then + print ("</dd>") + end + end + end + end +end + + +-- This function prints the main menu, with the given prefix/controller "selected" +-- returns the group, category, subcat that is selected +function render_mainmenu ( menu, prefix, controller, action ) + -- prefix+controller defines which menu is "selected" + local megroup = nil + local mecat = nil + local mesubcat = nil + local liston = nil + local group = "" + local cat = "" + + -- find the current group/cat/subcat + for i=1,table.maxn(menu) do + if (menu[i].prefix == prefix) and ( menu[i].controller == controller ) then + megroup = menu[i].group + mecat = menu[i].cat + if ( menu[i].action == action ) then + mesubcat = menu[i].subcat + elseif ( menu[i].action == "*" ) and ( mesubcat == nil ) then + mesubcat = menu[i].subcat + end + end + end + + -- render the mainmenu + local thisgroup = "" + local thiscat = "" + for i=1,table.maxn(menu),1 do + if menu[i].group ~= thisgroup then + thisgroup = menu[i].group + if ( liston ) then io.write ("</ul>") end + print ( html.entity ( "h3", menu[i].group ) ) + io.write("<ul>") + liston = true + thicat = nil + end + if menu[i].cat ~= thiscat then + thiscat = menu[i].cat + if (thiscat == mecat ) then + print ( html.entity ("li", html.html_escape(thiscat), nil, "selected")) + else + print (html.link ( { value= ENV.SCRIPT_NAME .. menu[i].uri , + label = html.entity ("li", html.html_escape(thiscat)) } ) ) + end + end + end + io.write ("</ul>") + return megroup, mecat, mesubcat +end + + + +-- This function prints the tabs for the submenu, with the given submenu "selected" +function render_submenu ( menu, group, cat, subcat ) + cat = cat or "" + group = group or "" + local this_subcat = nil + local foo = group .. " > " .. cat + if (foo ~= " > " ) then + print ( html.entity ( "h2", html.html_escape( group .. " > " .. cat ) )) + end + + + -- print (string.format ("%s - %s - %s", group, cat , (subcat or ""))) + + io.write ("<ul>") + for i=1, table.maxn(menu),1 do + if ( group == menu[i].group ) and ( cat == menu[i].cat ) then + -- If a subcat was not selected, make the first one the default + if ( subcat == nil ) then + subcat = menu[i].subcat + end + + if ( menu[i].subcat ~= this_subcat ) then + this_subcat = menu[i].subcat + if ( menu[i].subcat == subcat ) then + print ( html.entity ("li", menu[i].subcat, nil, "selected")) + else + io.write ("<li>") + io.write ( html.link ( { value= ENV.SCRIPT_NAME .. menu[i].uri , + label =menu[i].subcat } ) ) + print ("</li>") + end + end + end + end + io.write ("</ul>") +end |