diff options
author | Sven Roederer <freifunk@it-solutions.geroedel.de> | 2019-07-01 21:52:07 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2020-07-19 20:14:22 +0200 |
commit | 8b8d83e42dd3d10d82e29a8614a7b3e3e94b16c6 (patch) | |
tree | 5e4009d43428da534cb231e91c68dd4eb2b3a258 /libs/luci-lib-base/luasrc | |
parent | 3b2a1e9e1129e77c4ebd5e7ace35653969f2d515 (diff) |
luci-base: move some generic classes into a separate luci-base-libs package
The new package luci-base-libs provides the modules that not strictly relate
to the web-interface of luci. By separating these libs they can be used by
other packages without having to install the web-components.
This change was inspired by providing a shell-only interface for 4MB-flash
devices, by keeping as much code common with a full install.
Signed-off-by: Sven Roederer <freifunk@it-solutions.geroedel.de>
Diffstat (limited to 'libs/luci-lib-base/luasrc')
-rw-r--r-- | libs/luci-lib-base/luasrc/debug.lua | 37 | ||||
-rw-r--r-- | libs/luci-lib-base/luasrc/http.lua | 554 | ||||
-rw-r--r-- | libs/luci-lib-base/luasrc/http.luadoc | 260 | ||||
-rw-r--r-- | libs/luci-lib-base/luasrc/ltn12.lua | 316 | ||||
-rw-r--r-- | libs/luci-lib-base/luasrc/util.lua | 776 | ||||
-rw-r--r-- | libs/luci-lib-base/luasrc/util.luadoc | 413 |
6 files changed, 2356 insertions, 0 deletions
diff --git a/libs/luci-lib-base/luasrc/debug.lua b/libs/luci-lib-base/luasrc/debug.lua new file mode 100644 index 000000000..8ff1bb698 --- /dev/null +++ b/libs/luci-lib-base/luasrc/debug.lua @@ -0,0 +1,37 @@ +local debug = require "debug" +local io = require "io" +local collectgarbage, floor = collectgarbage, math.floor + +module "luci.debug" +__file__ = debug.getinfo(1, 'S').source:sub(2) + +-- Enables the memory tracer with given flags and returns a function to disable the tracer again +function trap_memtrace(flags, dest) + flags = flags or "clr" + local tracefile = io.open(dest or "/tmp/memtrace", "w") + local peak = 0 + + local function trap(what, line) + local info = debug.getinfo(2, "Sn") + local size = floor(collectgarbage("count")) + if size > peak then + peak = size + end + if tracefile then + tracefile:write( + "[", what, "] ", info.source, ":", (line or "?"), "\t", + (info.namewhat or ""), "\t", + (info.name or ""), "\t", + size, " (", peak, ")\n" + ) + end + end + + debug.sethook(trap, flags) + + return function() + debug.sethook() + tracefile:close() + end +end + diff --git a/libs/luci-lib-base/luasrc/http.lua b/libs/luci-lib-base/luasrc/http.lua new file mode 100644 index 000000000..20b55f285 --- /dev/null +++ b/libs/luci-lib-base/luasrc/http.lua @@ -0,0 +1,554 @@ +-- Copyright 2008 Steven Barth <steven@midlink.org> +-- Copyright 2010-2018 Jo-Philipp Wich <jo@mein.io> +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +local coroutine = require "coroutine" +local table = require "table" +local lhttp = require "lucihttp" +local nixio = require "nixio" +local ltn12 = require "luci.ltn12" + +local table, ipairs, pairs, type, tostring, tonumber, error = + table, ipairs, pairs, type, tostring, tonumber, error + +module "luci.http" + +HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size + +context = util.threadlocal() + +Request = util.class() +function Request.__init__(self, env, sourcein, sinkerr) + self.input = sourcein + self.error = sinkerr + + + -- File handler nil by default to let .content() work + self.filehandler = nil + + -- HTTP-Message table + self.message = { + env = env, + headers = {}, + params = urldecode_params(env.QUERY_STRING or ""), + } + + self.parsed_input = false +end + +function Request.formvalue(self, name, noparse) + if not noparse and not self.parsed_input then + self:_parse_input() + end + + if name then + return self.message.params[name] + else + return self.message.params + end +end + +function Request.formvaluetable(self, prefix) + local vals = {} + prefix = prefix and prefix .. "." or "." + + if not self.parsed_input then + self:_parse_input() + end + + local void = self.message.params[nil] + for k, v in pairs(self.message.params) do + if k:find(prefix, 1, true) == 1 then + vals[k:sub(#prefix + 1)] = tostring(v) + end + end + + return vals +end + +function Request.content(self) + if not self.parsed_input then + self:_parse_input() + end + + return self.message.content, self.message.content_length +end + +function Request.getcookie(self, name) + return lhttp.header_attribute("cookie; " .. (self:getenv("HTTP_COOKIE") or ""), name) +end + +function Request.getenv(self, name) + if name then + return self.message.env[name] + else + return self.message.env + end +end + +function Request.setfilehandler(self, callback) + self.filehandler = callback + + if not self.parsed_input then + return + end + + -- If input has already been parsed then uploads are stored as unlinked + -- temporary files pointed to by open file handles in the parameter + -- value table. Loop all params, and invoke the file callback for any + -- param with an open file handle. + local name, value + for name, value in pairs(self.message.params) do + if type(value) == "table" then + while value.fd do + local data = value.fd:read(1024) + local eof = (not data or data == "") + + callback(value, data, eof) + + if eof then + value.fd:close() + value.fd = nil + end + end + end + end +end + +function Request._parse_input(self) + parse_message_body( + self.input, + self.message, + self.filehandler + ) + self.parsed_input = true +end + +function close() + if not context.eoh then + context.eoh = true + coroutine.yield(3) + end + + if not context.closed then + context.closed = true + coroutine.yield(5) + end +end + +function content() + return context.request:content() +end + +function formvalue(name, noparse) + return context.request:formvalue(name, noparse) +end + +function formvaluetable(prefix) + return context.request:formvaluetable(prefix) +end + +function getcookie(name) + return context.request:getcookie(name) +end + +-- or the environment table itself. +function getenv(name) + return context.request:getenv(name) +end + +function setfilehandler(callback) + return context.request:setfilehandler(callback) +end + +function header(key, value) + if not context.headers then + context.headers = {} + end + context.headers[key:lower()] = value + coroutine.yield(2, key, value) +end + +function prepare_content(mime) + if not context.headers or not context.headers["content-type"] then + if mime == "application/xhtml+xml" then + if not getenv("HTTP_ACCEPT") or + not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then + mime = "text/html; charset=UTF-8" + end + header("Vary", "Accept") + end + header("Content-Type", mime) + end +end + +function source() + return context.request.input +end + +function status(code, message) + code = code or 200 + message = message or "OK" + context.status = code + coroutine.yield(1, code, message) +end + +-- This function is as a valid LTN12 sink. +-- If the content chunk is nil this function will automatically invoke close. +function write(content, src_err) + if not content then + if src_err then + error(src_err) + else + close() + end + return true + elseif #content == 0 then + return true + else + if not context.eoh then + if not context.status then + status() + end + if not context.headers or not context.headers["content-type"] then + header("Content-Type", "text/html; charset=utf-8") + end + if not context.headers["cache-control"] then + header("Cache-Control", "no-cache") + header("Expires", "0") + end + if not context.headers["x-frame-options"] then + header("X-Frame-Options", "SAMEORIGIN") + end + if not context.headers["x-xss-protection"] then + header("X-XSS-Protection", "1; mode=block") + end + if not context.headers["x-content-type-options"] then + header("X-Content-Type-Options", "nosniff") + end + + context.eoh = true + coroutine.yield(3) + end + coroutine.yield(4, content) + return true + end +end + +function splice(fd, size) + coroutine.yield(6, fd, size) +end + +function redirect(url) + if url == "" then url = "/" end + status(302, "Found") + header("Location", url) + close() +end + +function build_querystring(q) + local s, n, k, v = {}, 1, nil, nil + + for k, v in pairs(q) do + s[n+0] = (n == 1) and "?" or "&" + s[n+1] = util.urlencode(k) + s[n+2] = "=" + s[n+3] = util.urlencode(v) + n = n + 4 + end + + return table.concat(s, "") +end + +urldecode = util.urldecode + +urlencode = util.urlencode + +function write_json(x) + util.serialize_json(x, write) +end + +-- from given url or string. Returns a table with urldecoded values. +-- Simple parameters are stored as string values associated with the parameter +-- name within the table. Parameters with multiple values are stored as array +-- containing the corresponding values. +function urldecode_params(url, tbl) + local parser, name + local params = tbl or { } + + parser = lhttp.urlencoded_parser(function (what, buffer, length) + if what == parser.TUPLE then + name, value = nil, nil + elseif what == parser.NAME then + name = lhttp.urldecode(buffer) + elseif what == parser.VALUE and name then + params[name] = lhttp.urldecode(buffer) or "" + end + + return true + end) + + if parser then + parser:parse((url or ""):match("[^?]*$")) + parser:parse(nil) + end + + return params +end + +-- separated by "&". Tables are encoded as parameters with multiple values by +-- repeating the parameter name with each value. +function urlencode_params(tbl) + local k, v + local n, enc = 1, {} + for k, v in pairs(tbl) do + if type(v) == "table" then + local i, v2 + for i, v2 in ipairs(v) do + if enc[1] then + enc[n] = "&" + n = n + 1 + end + + enc[n+0] = lhttp.urlencode(k) + enc[n+1] = "=" + enc[n+2] = lhttp.urlencode(v2) + n = n + 3 + end + else + if enc[1] then + enc[n] = "&" + n = n + 1 + end + + enc[n+0] = lhttp.urlencode(k) + enc[n+1] = "=" + enc[n+2] = lhttp.urlencode(v) + n = n + 3 + end + end + + return table.concat(enc, "") +end + +-- Content-Type. Stores all extracted data associated with its parameter name +-- in the params table within the given message object. Multiple parameter +-- values are stored as tables, ordinary ones as strings. +-- If an optional file callback function is given then it is fed with the +-- file contents chunk by chunk and only the extracted file name is stored +-- within the params table. The callback function will be called subsequently +-- with three arguments: +-- o Table containing decoded (name, file) and raw (headers) mime header data +-- o String value containing a chunk of the file data +-- o Boolean which indicates whether the current chunk is the last one (eof) +function mimedecode_message_body(src, msg, file_cb) + local parser, header, field + local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil) + + parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length) + if what == parser.PART_INIT then + field = { } + + elseif what == parser.HEADER_NAME then + header = buffer:lower() + + elseif what == parser.HEADER_VALUE and header then + if header:lower() == "content-disposition" and + lhttp.header_attribute(buffer, nil) == "form-data" + then + field.name = lhttp.header_attribute(buffer, "name") + field.file = lhttp.header_attribute(buffer, "filename") + field[1] = field.file + end + + if field.headers then + field.headers[header] = buffer + else + field.headers = { [header] = buffer } + end + + elseif what == parser.PART_BEGIN then + return not field.file + + elseif what == parser.PART_DATA and field.name and length > 0 then + if field.file then + if file_cb then + file_cb(field, buffer, false) + msg.params[field.name] = msg.params[field.name] or field + else + if not field.fd then + field.fd = nixio.mkstemp(field.name) + end + + if field.fd then + field.fd:write(buffer) + msg.params[field.name] = msg.params[field.name] or field + end + end + else + field.value = buffer + end + + elseif what == parser.PART_END and field.name then + if field.file and msg.params[field.name] then + if file_cb then + file_cb(field, "", true) + elseif field.fd then + field.fd:seek(0, "set") + end + else + local val = msg.params[field.name] + + if type(val) == "table" then + val[#val+1] = field.value or "" + elseif val ~= nil then + msg.params[field.name] = { val, field.value or "" } + else + msg.params[field.name] = field.value or "" + end + end + + field = nil + + elseif what == parser.ERROR then + err = buffer + end + + return true + end, HTTP_MAX_CONTENT) + + return ltn12.pump.all(src, function (chunk) + len = len + (chunk and #chunk or 0) + + if maxlen and len > maxlen + 2 then + return nil, "Message body size exceeds Content-Length" + end + + if not parser or not parser:parse(chunk) then + return nil, err + end + + return true + end) +end + +-- Content-Type. Stores all extracted data associated with its parameter name +-- in the params table within the given message object. Multiple parameter +-- values are stored as tables, ordinary ones as strings. +function urldecode_message_body(src, msg) + local err, name, value, parser + local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil) + + parser = lhttp.urlencoded_parser(function (what, buffer, length) + if what == parser.TUPLE then + name, value = nil, nil + elseif what == parser.NAME then + name = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) + elseif what == parser.VALUE and name then + local val = msg.params[name] + + if type(val) == "table" then + val[#val+1] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" + elseif val ~= nil then + msg.params[name] = { val, lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" } + else + msg.params[name] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" + end + elseif what == parser.ERROR then + err = buffer + end + + return true + end, HTTP_MAX_CONTENT) + + return ltn12.pump.all(src, function (chunk) + len = len + (chunk and #chunk or 0) + + if maxlen and len > maxlen + 2 then + return nil, "Message body size exceeds Content-Length" + elseif len > HTTP_MAX_CONTENT then + return nil, "Message body size exceeds maximum allowed length" + end + + if not parser or not parser:parse(chunk) then + return nil, err + end + + return true + end) +end + +-- This function will examine the Content-Type within the given message object +-- to select the appropriate content decoder. +-- Currently the application/x-www-urlencoded and application/form-data +-- mime types are supported. If the encountered content encoding can't be +-- handled then the whole message body will be stored unaltered as "content" +-- property within the given message object. +function parse_message_body(src, msg, filecb) + if msg.env.CONTENT_LENGTH or msg.env.REQUEST_METHOD == "POST" then + local ctype = lhttp.header_attribute(msg.env.CONTENT_TYPE, nil) + + -- Is it multipart/mime ? + if ctype == "multipart/form-data" then + return mimedecode_message_body(src, msg, filecb) + + -- Is it application/x-www-form-urlencoded ? + elseif ctype == "application/x-www-form-urlencoded" then + return urldecode_message_body(src, msg) + + end + + -- Unhandled encoding + -- If a file callback is given then feed it chunk by chunk, else + -- store whole buffer in message.content + local sink + + -- If we have a file callback then feed it + if type(filecb) == "function" then + local meta = { + name = "raw", + encoding = msg.env.CONTENT_TYPE + } + sink = function( chunk ) + if chunk then + return filecb(meta, chunk, false) + else + return filecb(meta, nil, true) + end + end + -- ... else append to .content + else + msg.content = "" + msg.content_length = 0 + + sink = function( chunk ) + if chunk then + if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then + msg.content = msg.content .. chunk + msg.content_length = msg.content_length + #chunk + return true + else + return nil, "POST data exceeds maximum allowed length" + end + end + return true + end + end + + -- Pump data... + while true do + local ok, err = ltn12.pump.step( src, sink ) + + if not ok and err then + return nil, err + elseif not ok then -- eof + return true + end + end + + return true + end + + return false +end diff --git a/libs/luci-lib-base/luasrc/http.luadoc b/libs/luci-lib-base/luasrc/http.luadoc new file mode 100644 index 000000000..8f6f380d8 --- /dev/null +++ b/libs/luci-lib-base/luasrc/http.luadoc @@ -0,0 +1,260 @@ +---[[ +LuCI Web Framework high-level HTTP functions. +]] +module "luci.http" + +---[[ +Close the HTTP-Connection. + +@class function +@name close +]] + +---[[ +Return the request content if the request was of unknown type. + +@class function +@name content +@return HTTP request body +@return HTTP request body length +]] + +---[[ +Get a certain HTTP input value or a table of all input values. + +@class function +@name formvalue +@param name Name of the GET or POST variable to fetch +@param noparse Don't parse POST data before getting the value +@return HTTP input value or table of all input value +]] + +---[[ +Get a table of all HTTP input values with a certain prefix. + +@class function +@name formvaluetable +@param prefix Prefix +@return Table of all HTTP input values with given prefix +]] + +---[[ +Get the value of a certain HTTP-Cookie. + +@class function +@name getcookie +@param name Cookie Name +@return String containing cookie data +]] + +---[[ +Get the value of a certain HTTP environment variable +or the environment table itself. + +@class function +@name getenv +@param name Environment variable +@return HTTP environment value or environment table +]] + +---[[ +Set a handler function for incoming user file uploads. + +@class function +@name setfilehandler +@param callback Handler function +]] + +---[[ +Send a HTTP-Header. + +@class function +@name header +@param key Header key +@param value Header value +]] + +---[[ +Set the mime type of following content data. + +@class function +@name prepare_content +@param mime Mimetype of following content +]] + +---[[ +Get the RAW HTTP input source + +@class function +@name source +@return HTTP LTN12 source +]] + +---[[ +Set the HTTP status code and status message. + +@class function +@name status +@param code Status code +@param message Status message +]] + +---[[ +Send a chunk of content data to the client. + +This function is as a valid LTN12 sink. +If the content chunk is nil this function will automatically invoke close. + +@class function +@name write +@param content Content chunk +@param src_err Error object from source (optional) +@see close +]] + +---[[ +Splice data from a filedescriptor to the client. + +@class function +@name splice +@param fp File descriptor +@param size Bytes to splice (optional) +]] + +---[[ +Redirects the client to a new URL and closes the connection. + +@class function +@name redirect +@param url Target URL +]] + +---[[ +Create a querystring out of a table of key - value pairs. + +@class function +@name build_querystring +@param table Query string source table +@return Encoded HTTP query string +]] + +---[[ +Return the URL-decoded equivalent of a string. + +@class function +@name urldecode +@param str URL-encoded string +@param no_plus Don't decode + to " " +@return URL-decoded string +@see urlencode +]] + +---[[ +Return the URL-encoded equivalent of a string. + +@class function +@name urlencode +@param str Source string +@return URL-encoded string +@see urldecode +]] + +---[[ +Send the given data as JSON encoded string. + +@class function +@name write_json +@param data Data to send +]] + +---[[ +Extract and split urlencoded data pairs, separated bei either "&" or ";" +from given url or string. Returns a table with urldecoded values. + +Simple parameters are stored as string values associated with the parameter +name within the table. Parameters with multiple values are stored as array +containing the corresponding values. + +@class function +@name urldecode_params +@param url The url or string which contains x-www-urlencoded form data +@param tbl Use the given table for storing values (optional) +@return Table containing the urldecoded parameters +@see urlencode_params +]] + +---[[ +Encode each key-value-pair in given table to x-www-urlencoded format, +separated by "&". + +Tables are encoded as parameters with multiple values by repeating the +parameter name with each value. + +@class function +@name urlencode_params +@param tbl Table with the values +@return String containing encoded values +@see urldecode_params +]] + +---[[ +Decode a mime encoded http message body with multipart/form-data Content-Type. + +Stores all extracted data associated with its parameter name +in the params table within the given message object. Multiple parameter +values are stored as tables, ordinary ones as strings. + +If an optional file callback function is given then it is fed with the +file contents chunk by chunk and only the extracted file name is stored +within the params table. The callback function will be called subsequently +with three arguments: + o Table containing decoded (name, file) and raw (headers) mime header data + o String value containing a chunk of the file data + o Boolean which indicates whether the current chunk is the last one (eof) + +@class function +@name mimedecode_message_body +@param src Ltn12 source function +@param msg HTTP message object +@param filecb File callback function (optional) +@return Value indicating successful operation (not nil means "ok") +@return String containing the error if unsuccessful +@see parse_message_header +]] + +---[[ +Decode an urlencoded http message body with application/x-www-urlencoded +Content-Type. + +Stores all extracted data associated with its parameter name in the params +table within the given message object. Multiple parameter values are stored +as tables, ordinary ones as strings. + +@class function +@name urldecode_message_body +@param src Ltn12 source function +@param msg HTTP message object +@return Value indicating successful operation (not nil means "ok") +@return String containing the error if unsuccessful +@see parse_message_header +]] + +---[[ +Try to extract and decode a http message body from the given ltn12 source. +This function will examine the Content-Type within the given message object +to select the appropriate content decoder. + +Currently the application/x-www-urlencoded and application/form-data +mime types are supported. If the encountered content encoding can't be +handled then the whole message body will be stored unaltered as "content" +property within the given message object. + +@class function +@name parse_message_body +@param src Ltn12 source function +@param msg HTTP message object +@param filecb File data callback (optional, see mimedecode_message_body()) +@return Value indicating successful operation (not nil means "ok") +@return String containing the error if unsuccessful +@see parse_message_header +]] diff --git a/libs/luci-lib-base/luasrc/ltn12.lua b/libs/luci-lib-base/luasrc/ltn12.lua new file mode 100644 index 000000000..3a7268cca --- /dev/null +++ b/libs/luci-lib-base/luasrc/ltn12.lua @@ -0,0 +1,316 @@ +--[[ +LuaSocket 2.0.2 license +Copyright � 2004-2007 Diego Nehab + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +]]-- +--[[ + Changes made by LuCI project: + * Renamed to luci.ltn12 to avoid collisions with luasocket + * Added inline documentation +]]-- +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local base = _G + +-- See http://lua-users.org/wiki/FiltersSourcesAndSinks for design concepts +module("luci.ltn12") + +filter = {} +source = {} +sink = {} +pump = {} + +-- 2048 seems to be better in windows... +BLOCKSIZE = 2048 +_VERSION = "LTN12 1.0.1" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- + + +-- by passing it each chunk and updating a context between calls. +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local n = table.getn(arg) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- + + +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +function source.error(err) + return function() + return nil, err + end +end + +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(BLOCKSIZE) + if chunk and chunk:len() == 0 then chunk = nil end + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+BLOCKSIZE-1) + i = i + BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + t[#t+1] = chunk + end + end +end + +function source.chain(src, f) + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- Sources will be used one after the other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- + + +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then t[#t+1] = chunk end + return 1 + end + return f, t +end + +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +function sink.error(err) + return function() + return nil, err + end +end + +function sink.chain(f, snk) + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- + + +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + diff --git a/libs/luci-lib-base/luasrc/util.lua b/libs/luci-lib-base/luasrc/util.lua new file mode 100644 index 000000000..a30e8b72f --- /dev/null +++ b/libs/luci-lib-base/luasrc/util.lua @@ -0,0 +1,776 @@ +-- Copyright 2008 Steven Barth <steven@midlink.org> +-- Licensed to the public under the Apache License 2.0. + +local io = require "io" +local math = require "math" +local table = require "table" +local debug = require "debug" +local ldebug = require "luci.debug" +local string = require "string" +local coroutine = require "coroutine" +local tparser = require "luci.template.parser" +local json = require "luci.jsonc" +local lhttp = require "lucihttp" + +local _ubus = require "ubus" +local _ubus_connection = nil + +local getmetatable, setmetatable = getmetatable, setmetatable +local rawget, rawset, unpack, select = rawget, rawset, unpack, select +local tostring, type, assert, error = tostring, type, assert, error +local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring +local require, pcall, xpcall = require, pcall, xpcall +local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit + +module "luci.util" + +-- +-- Pythonic string formatting extension +-- +getmetatable("").__mod = function(a, b) + local ok, res + + if not b then + return a + elseif type(b) == "table" then + local k, _ + for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end + + ok, res = pcall(a.format, a, unpack(b)) + if not ok then + error(res, 2) + end + return res + else + if type(b) == "userdata" then b = tostring(b) end + + ok, res = pcall(a.format, a, b) + if not ok then + error(res, 2) + end + return res + end +end + + +-- +-- Class helper routines +-- + +-- Instantiates a class +local function _instantiate(class, ...) + local inst = setmetatable({}, {__index = class}) + + if inst.__init__ then + inst:__init__(...) + end + + return inst +end + +-- The class object can be instantiated by calling itself. +-- Any class functions or shared parameters can be attached to this object. +-- Attaching a table to the class object makes this table shared between +-- all instances of this class. For object parameters use the __init__ function. +-- Classes can inherit member functions and values from a base class. +-- Class can be instantiated by calling them. All parameters will be passed +-- to the __init__ function of this class - if such a function exists. +-- The __init__ function must be used to set any object parameters that are not shared +-- with other objects of this class. Any return values will be ignored. +function class(base) + return setmetatable({}, { + __call = _instantiate, + __index = base + }) +end + +function instanceof(object, class) + local meta = getmetatable(object) + while meta and meta.__index do + if meta.__index == class then + return true + end + meta = getmetatable(meta.__index) + end + return false +end + + +-- +-- Scope manipulation routines +-- + +coxpt = setmetatable({}, { __mode = "kv" }) + +local tl_meta = { + __mode = "k", + + __index = function(self, key) + local t = rawget(self, coxpt[coroutine.running()] + or coroutine.running() or 0) + return t and t[key] + end, + + __newindex = function(self, key, value) + local c = coxpt[coroutine.running()] or coroutine.running() or 0 + local r = rawget(self, c) + if not r then + rawset(self, c, { [key] = value }) + else + r[key] = value + end + end +} + +-- the current active coroutine. A thread local store is private a table object +-- whose values can't be accessed from outside of the running coroutine. +function threadlocal(tbl) + return setmetatable(tbl or {}, tl_meta) +end + + +-- +-- Debugging routines +-- + +function perror(obj) + return io.stderr:write(tostring(obj) .. "\n") +end + +function dumptable(t, maxdepth, i, seen) + i = i or 0 + seen = seen or setmetatable({}, {__mode="k"}) + + for k,v in pairs(t) do + perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v)) + if type(v) == "table" and (not maxdepth or i < maxdepth) then + if not seen[v] then + seen[v] = true + dumptable(v, maxdepth, i+1, seen) + else + perror(string.rep("\t", i) .. "*** RECURSION ***") + end + end + end +end + + +-- +-- String and data manipulation routines +-- + +function pcdata(value) + return value and tparser.pcdata(tostring(value)) +end + +function urlencode(value) + if value ~= nil then + local str = tostring(value) + return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL) + or str + end + return nil +end + +function urldecode(value, decode_plus) + if value ~= nil then + local flag = decode_plus and lhttp.DECODE_PLUS or 0 + local str = tostring(value) + return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED + flag) + or str + end + return nil +end + +function striptags(value) + return value and tparser.striptags(tostring(value)) +end + +function shellquote(value) + return string.format("'%s'", string.gsub(value or "", "'", "'\\''")) +end + +-- for bash, ash and similar shells single-quoted strings are taken +-- literally except for single quotes (which terminate the string) +-- (and the exception noted below for dash (-) at the start of a +-- command line parameter). +function shellsqescape(value) + local res + res, _ = string.gsub(value, "'", "'\\''") + return res +end + +-- bash, ash and other similar shells interpret a dash (-) at the start +-- of a command-line parameters as an option indicator regardless of +-- whether it is inside a single-quoted string. It must be backlash +-- escaped to resolve this. This requires in some funky special-case +-- handling. It may actually be a property of the getopt function +-- rather than the shell proper. +function shellstartsqescape(value) + res, _ = string.gsub(value, "^%-", "\\-") + return shellsqescape(res) +end + +-- containing the resulting substrings. The optional max parameter specifies +-- the number of bytes to process, regardless of the actual length of the given +-- string. The optional last parameter, regex, specifies whether the separator +-- sequence is interpreted as regular expression. +-- pattern as regular expression (optional, default is false) +function split(str, pat, max, regex) + pat = pat or "\n" + max = max or #str + + local t = {} + local c = 1 + + if #str == 0 then + return {""} + end + + if #pat == 0 then + return nil + end + + if max == 0 then + return str + end + + repeat + local s, e = str:find(pat, c, not regex) + max = max - 1 + if s and max < 0 then + t[#t+1] = str:sub(c) + else + t[#t+1] = str:sub(c, s and s - 1) + end + c = e and e + 1 or #str + 1 + until not s or max < 0 + + return t +end + +function trim(str) + return (str:gsub("^%s*(.-)%s*$", "%1")) +end + +function cmatch(str, pat) + local count = 0 + for _ in str:gmatch(pat) do count = count + 1 end + return count +end + +-- one token per invocation, the tokens are separated by whitespace. If the +-- input value is a table, it is transformed into a string first. A nil value +-- will result in a valid iterator which aborts with the first invocation. +function imatch(v) + if type(v) == "table" then + local k = nil + return function() + k = next(v, k) + return v[k] + end + + elseif type(v) == "number" or type(v) == "boolean" then + local x = true + return function() + if x then + x = false + return tostring(v) + end + end + + elseif type(v) == "userdata" or type(v) == "string" then + return tostring(v):gmatch("%S+") + end + + return function() end +end + +-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant. +-- Recognized units are: +-- o "y" - one year (60*60*24*366) +-- o "m" - one month (60*60*24*31) +-- o "w" - one week (60*60*24*7) +-- o "d" - one day (60*60*24) +-- o "h" - one hour (60*60) +-- o "min" - one minute (60) +-- o "kb" - one kilobyte (1024) +-- o "mb" - one megabyte (1024*1024) +-- o "gb" - one gigabyte (1024*1024*1024) +-- o "kib" - one si kilobyte (1000) +-- o "mib" - one si megabyte (1000*1000) +-- o "gib" - one si gigabyte (1000*1000*1000) +function parse_units(ustr) + + local val = 0 + + -- unit map + local map = { + -- date stuff + y = 60 * 60 * 24 * 366, + m = 60 * 60 * 24 * 31, + w = 60 * 60 * 24 * 7, + d = 60 * 60 * 24, + h = 60 * 60, + min = 60, + + -- storage sizes + kb = 1024, + mb = 1024 * 1024, + gb = 1024 * 1024 * 1024, + + -- storage sizes (si) + kib = 1000, + mib = 1000 * 1000, + gib = 1000 * 1000 * 1000 + } + + -- parse input string + for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do + + local num = spec:gsub("[^0-9%.]+$","") + local spn = spec:gsub("^[0-9%.]+", "") + + if map[spn] or map[spn:sub(1,1)] then + val = val + num * ( map[spn] or map[spn:sub(1,1)] ) + else + val = val + num + end + end + + + return val +end + +-- also register functions above in the central string class for convenience +string.pcdata = pcdata +string.striptags = striptags +string.split = split +string.trim = trim +string.cmatch = cmatch +string.parse_units = parse_units + + +function append(src, ...) + for i, a in ipairs({...}) do + if type(a) == "table" then + for j, v in ipairs(a) do + src[#src+1] = v + end + else + src[#src+1] = a + end + end + return src +end + +function combine(...) + return append({}, ...) +end + +function contains(table, value) + for k, v in pairs(table) do + if value == v then + return k + end + end + return false +end + +-- Both table are - in fact - merged together. +function update(t, updates) + for k, v in pairs(updates) do + t[k] = v + end +end + +function keys(t) + local keys = { } + if t then + for k, _ in kspairs(t) do + keys[#keys+1] = k + end + end + return keys +end + +function clone(object, deep) + local copy = {} + + for k, v in pairs(object) do + if deep and type(v) == "table" then + v = clone(v, deep) + end + copy[k] = v + end + + return setmetatable(copy, getmetatable(object)) +end + + +-- Serialize the contents of a table value. +function _serialize_table(t, seen) + assert(not seen[t], "Recursion detected.") + seen[t] = true + + local data = "" + local idata = "" + local ilen = 0 + + for k, v in pairs(t) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then + k = serialize_data(k, seen) + v = serialize_data(v, seen) + data = data .. ( #data > 0 and ", " or "" ) .. + '[' .. k .. '] = ' .. v + elseif k > ilen then + ilen = k + end + end + + for i = 1, ilen do + local v = serialize_data(t[i], seen) + idata = idata .. ( #idata > 0 and ", " or "" ) .. v + end + + return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data +end + +-- with loadstring(). +function serialize_data(val, seen) + seen = seen or setmetatable({}, {__mode="k"}) + + if val == nil then + return "nil" + elseif type(val) == "number" then + return val + elseif type(val) == "string" then + return "%q" % val + elseif type(val) == "boolean" then + return val and "true" or "false" + elseif type(val) == "function" then + return "loadstring(%q)" % get_bytecode(val) + elseif type(val) == "table" then + return "{ " .. _serialize_table(val, seen) .. " }" + else + return '"[unhandled data type:' .. type(val) .. ']"' + end +end + +function restore_data(str) + return loadstring("return " .. str)() +end + + +-- +-- Byte code manipulation routines +-- + +-- will be stripped before it is returned. +function get_bytecode(val) + local code + + if type(val) == "function" then + code = string.dump(val) + else + code = string.dump( loadstring( "return " .. serialize_data(val) ) ) + end + + return code -- and strip_bytecode(code) +end + +-- numbers and debugging numbers will be discarded. Original version by +-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html) +function strip_bytecode(code) + local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12) + local subint + if endian == 1 then + subint = function(code, i, l) + local val = 0 + for n = l, 1, -1 do + val = val * 256 + code:byte(i + n - 1) + end + return val, i + l + end + else + subint = function(code, i, l) + local val = 0 + for n = 1, l, 1 do + val = val * 256 + code:byte(i + n - 1) + end + return val, i + l + end + end + + local function strip_function(code) + local count, offset = subint(code, 1, size) + local stripped = { string.rep("\0", size) } + local dirty = offset + count + offset = offset + count + int * 2 + 4 + offset = offset + int + subint(code, offset, int) * ins + count, offset = subint(code, offset, int) + for n = 1, count do + local t + t, offset = subint(code, offset, 1) + if t == 1 then + offset = offset + 1 + elseif t == 4 then + offset = offset + size + subint(code, offset, size) + elseif t == 3 then + offset = offset + num + elseif t == 254 or t == 9 then + offset = offset + lnum + end + end + count, offset = subint(code, offset, int) + stripped[#stripped+1] = code:sub(dirty, offset - 1) + for n = 1, count do + local proto, off = strip_function(code:sub(offset, -1)) + stripped[#stripped+1] = proto + offset = offset + off - 1 + end + offset = offset + subint(code, offset, int) * int + int + count, offset = subint(code, offset, int) + for n = 1, count do + offset = offset + subint(code, offset, size) + size + int * 2 + end + count, offset = subint(code, offset, int) + for n = 1, count do + offset = offset + subint(code, offset, size) + size + end + stripped[#stripped+1] = string.rep("\0", int * 3) + return table.concat(stripped), offset + end + + return code:sub(1,12) .. strip_function(code:sub(13,-1)) +end + + +-- +-- Sorting iterator functions +-- + +function _sortiter( t, f ) + local keys = { } + + local k, v + for k, v in pairs(t) do + keys[#keys+1] = k + end + + local _pos = 0 + + table.sort( keys, f ) + + return function() + _pos = _pos + 1 + if _pos <= #keys then + return keys[_pos], t[keys[_pos]], _pos + end + end +end + +-- the provided callback function. +function spairs(t,f) + return _sortiter( t, f ) +end + +-- The table pairs are sorted by key. +function kspairs(t) + return _sortiter( t ) +end + +-- The table pairs are sorted by value. +function vspairs(t) + return _sortiter( t, function (a,b) return t[a] < t[b] end ) +end + + +-- +-- System utility functions +-- + +function bigendian() + return string.byte(string.dump(function() end), 7) == 0 +end + +function exec(command) + local pp = io.popen(command) + local data = pp:read("*a") + pp:close() + + return data +end + +function execi(command) + local pp = io.popen(command) + + return pp and function() + local line = pp:read() + + if not line then + pp:close() + end + + return line + end +end + +-- Deprecated +function execl(command) + local pp = io.popen(command) + local line = "" + local data = {} + + while true do + line = pp:read() + if (line == nil) then break end + data[#data+1] = line + end + pp:close() + + return data +end + + +local ubus_codes = { + "INVALID_COMMAND", + "INVALID_ARGUMENT", + "METHOD_NOT_FOUND", + "NOT_FOUND", + "NO_DATA", + "PERMISSION_DENIED", + "TIMEOUT", + "NOT_SUPPORTED", + "UNKNOWN_ERROR", + "CONNECTION_FAILED" +} + +local function ubus_return(...) + if select('#', ...) == 2 then + local rv, err = select(1, ...), select(2, ...) + if rv == nil and type(err) == "number" then + return nil, err, ubus_codes[err] + end + end + + return ... +end + +function ubus(object, method, data) + if not _ubus_connection then + _ubus_connection = _ubus.connect() + assert(_ubus_connection, "Unable to establish ubus connection") + end + + if object and method then + if type(data) ~= "table" then + data = { } + end + return ubus_return(_ubus_connection:call(object, method, data)) + elseif object then + return _ubus_connection:signatures(object) + else + return _ubus_connection:objects() + end +end + +function serialize_json(x, cb) + local js = json.stringify(x) + if type(cb) == "function" then + cb(js) + else + return js + end +end + + +function libpath() + return require "nixio.fs".dirname(ldebug.__file__) +end + +function checklib(fullpathexe, wantedlib) + local fs = require "nixio.fs" + local haveldd = fs.access('/usr/bin/ldd') + local haveexe = fs.access(fullpathexe) + if not haveldd or not haveexe then + return false + end + local libs = exec(string.format("/usr/bin/ldd %s", shellquote(fullpathexe))) + if not libs then + return false + end + for k, v in ipairs(split(libs)) do + if v:find(wantedlib) then + return true + end + end + return false +end + +------------------------------------------------------------------------------- +-- Coroutine safe xpcall and pcall versions +-- +-- Encapsulates the protected calls with a coroutine based loop, so errors can +-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines +-- yielding inside the call to pcall or xpcall. +-- +-- Authors: Roberto Ierusalimschy and Andre Carregal +-- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas +-- +-- Copyright 2005 - Kepler Project +-- +-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $ +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- Implements xpcall with coroutines +------------------------------------------------------------------------------- +local coromap = setmetatable({}, { __mode = "k" }) + +local function handleReturnValue(err, co, status, ...) + if not status then + return false, err(debug.traceback(co, (...)), ...) + end + if coroutine.status(co) == 'suspended' then + return performResume(err, co, coroutine.yield(...)) + else + return true, ... + end +end + +function performResume(err, co, ...) + return handleReturnValue(err, co, coroutine.resume(co, ...)) +end + +local function id(trace, ...) + return trace +end + +function coxpcall(f, err, ...) + local current = coroutine.running() + if not current then + if err == id then + return pcall(f, ...) + else + if select("#", ...) > 0 then + local oldf, params = f, { ... } + f = function() return oldf(unpack(params)) end + end + return xpcall(f, err) + end + else + local res, co = pcall(coroutine.create, f) + if not res then + local newf = function(...) return f(...) end + co = coroutine.create(newf) + end + coromap[co] = current + coxpt[co] = coxpt[current] or current or 0 + return performResume(err, co, ...) + end +end + +function copcall(f, ...) + return coxpcall(f, id, ...) +end diff --git a/libs/luci-lib-base/luasrc/util.luadoc b/libs/luci-lib-base/luasrc/util.luadoc new file mode 100644 index 000000000..4ec68dd1e --- /dev/null +++ b/libs/luci-lib-base/luasrc/util.luadoc @@ -0,0 +1,413 @@ +---[[ +LuCI utility functions. +]] +module "luci.util" + +---[[ +Create a Class object (Python-style object model). + +The class object can be instantiated by calling itself. +Any class functions or shared parameters can be attached to this object. +Attaching a table to the class object makes this table shared between +all instances of this class. For object parameters use the __init__ function. +Classes can inherit member functions and values from a base class. +Class can be instantiated by calling them. All parameters will be passed +to the __init__ function of this class - if such a function exists. +The __init__ function must be used to set any object parameters that are not shared +with other objects of this class. Any return values will be ignored. + +@class function +@name class +@param base The base class to inherit from (optional) +@return A class object +@see instanceof +@see clone +]] + +---[[ +Test whether the given object is an instance of the given class. + +@class function +@name instanceof +@param object Object instance +@param class Class object to test against +@return Boolean indicating whether the object is an instance +@see class +@see clone +]] + +---[[ +Create a new or get an already existing thread local store associated with +the current active coroutine. + +A thread local store is private a table object +whose values can't be accessed from outside of the running coroutine. + +@class function +@name threadlocal +@return Table value representing the corresponding thread local store +]] + +---[[ +Write given object to stderr. + +@class function +@name perror +@param obj Value to write to stderr +@return Boolean indicating whether the write operation was successful +]] + +---[[ +Recursively dumps a table to stdout, useful for testing and debugging. + +@class function +@name dumptable +@param t Table value to dump +@param maxdepth Maximum depth +@return Always nil +]] + +---[[ +Create valid XML PCDATA from given string. + +@class function +@name pcdata +@param value String value containing the data to escape +@return String value containing the escaped data +]] + +---[[ +Decode an URL-encoded string - optionally decoding the "+" sign to space. + +@class function +@name urldecode +@param str Input string in x-www-urlencoded format +@param decode_plus Decode "+" signs to spaces if true (optional) +@return The decoded string +@see urlencode +]] + +---[[ +URL-encode given string. + +@class function +@name urlencode +@param str String to encode +@return String containing the encoded data +@see urldecode +]] + +---[[ +Strip HTML tags from given string. + +@class function +@name striptags +@param value String containing the HTML text +@return String with HTML tags stripped of +]] + +---[[ +Safely quote value for use in shell commands. + +@class function +@name shellquote +@param value String containing the value to quote +@return Single-quote enclosed string with embedded quotes escaped +]] + +---[[ +Splits given string on a defined separator sequence and return a table +containing the resulting substrings. + +The optional max parameter specifies the number of bytes to process, +regardless of the actual length of the given string. The optional last +parameter, regex, specifies whether the separator sequence is +nterpreted as regular expression. + +@class function +@name split +@param str String value containing the data to split up +@param pat String with separator pattern (optional, defaults to "\n") +@param max Maximum times to split (optional) +@param regex Boolean indicating whether to interpret the separator +-- pattern as regular expression (optional, default is false) +@return Table containing the resulting substrings +]] + +---[[ +Remove leading and trailing whitespace from given string value. + +@class function +@name trim +@param str String value containing whitespace padded data +@return String value with leading and trailing space removed +]] + +---[[ +Count the occurrences of given substring in given string. + +@class function +@name cmatch +@param str String to search in +@param pattern String containing pattern to find +@return Number of found occurrences +]] + +---[[ +Return a matching iterator for the given value. + +The iterator will return one token per invocation, the tokens are separated by +whitespace. If the input value is a table, it is transformed into a string first. +A nil value will result in a valid iterator which aborts with the first invocation. + +@class function +@name imatch +@param val The value to scan (table, string or nil) +@return Iterator which returns one token per call +]] + +---[[ +Parse certain units from the given string and return the canonical integer +value or 0 if the unit is unknown. + +Upper- or lower case is irrelevant. +Recognized units are: + +-- o "y" - one year (60*60*24*366) + o "m" - one month (60*60*24*31) + o "w" - one week (60*60*24*7) + o "d" - one day (60*60*24) + o "h" - one hour (60*60) + o "min" - one minute (60) + o "kb" - one kilobyte (1024) + o "mb" - one megabyte (1024*1024) + o "gb" - one gigabyte (1024*1024*1024) + o "kib" - one si kilobyte (1000) + o "mib" - one si megabyte (1000*1000) + o "gib" - one si gigabyte (1000*1000*1000) + +@class function +@name parse_units +@param ustr String containing a numerical value with trailing unit +@return Number containing the canonical value +]] + +---[[ +Appends numerically indexed tables or single objects to a given table. + +@class function +@name append +@param src Target table +@param ... Objects to insert +@return Target table +]] + +---[[ +Combines two or more numerically indexed tables and single objects into one table. + +@class function +@name combine +@param tbl1 Table value to combine +@param tbl2 Table value to combine +@param ... More tables to combine +@return Table value containing all values of given tables +]] + +---[[ +Checks whether the given table contains the given value. + +@class function +@name contains +@param table Table value +@param value Value to search within the given table +@return Number indicating the first index at which the given value occurs +-- within table or false. +]] + +---[[ +Update values in given table with the values from the second given table. + +Both table are - in fact - merged together. + +@class function +@name update +@param t Table which should be updated +@param updates Table containing the values to update +@return Always nil +]] + +---[[ +Retrieve all keys of given associative table. + +@class function +@name keys +@param t Table to extract keys from +@return Sorted table containing the keys +]] + +---[[ +Clones the given object and return it's copy. + +@class function +@name clone +@param object Table value to clone +@param deep Boolean indicating whether to do recursive cloning +@return Cloned table value +]] + +---[[ +Recursively serialize given data to lua code, suitable for restoring +with loadstring(). + +@class function +@name serialize_data +@param val Value containing the data to serialize +@return String value containing the serialized code +@see restore_data +@see get_bytecode +]] + +---[[ +Restore data previously serialized with serialize_data(). + +@class function +@name restore_data +@param str String containing the data to restore +@return Value containing the restored data structure +@see serialize_data +@see get_bytecode +]] + +---[[ +Return the current runtime bytecode of the given data. The byte code +will be stripped before it is returned. + +@class function +@name get_bytecode +@param val Value to return as bytecode +@return String value containing the bytecode of the given data +]] + +---[[ +Strips unnecessary lua bytecode from given string. + +Information like line numbers and debugging numbers will be discarded. +Original version by Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html) + +@class function +@name strip_bytecode +@param code String value containing the original lua byte code +@return String value containing the stripped lua byte code +]] + +---[[ +Return a key, value iterator which returns the values sorted according to +the provided callback function. + +@class function +@name spairs +@param t The table to iterate +@param f A callback function to decide the order of elements +@return Function value containing the corresponding iterator +]] + +---[[ +Return a key, value iterator for the given table. + +The table pairs are sorted by key. + +@class function +@name kspairs +@param t The table to iterate +@return Function value containing the corresponding iterator +]] + +---[[ +Return a key, value iterator for the given table. + +The table pairs are sorted by value. + +@class function +@name vspairs +@param t The table to iterate +@return Function value containing the corresponding iterator +]] + +---[[ +Test whether the current system is operating in big endian mode. + +@class function +@name bigendian +@return Boolean value indicating whether system is big endian +]] + +---[[ +Execute given commandline and gather stdout. + +@class function +@name exec +@param command String containing command to execute +@return String containing the command's stdout +]] + +---[[ +Return a line-buffered iterator over the output of given command. + +@class function +@name execi +@param command String containing the command to execute +@return Iterator +]] + +---[[ +Issue an ubus call. + +@class function +@name ubus +@param object String containing the ubus object to call +@param method String containing the ubus method to call +@param values Table containing the values to pass +@return Table containin the ubus result +]] + +---[[ +Convert data structure to JSON + +@class function +@name serialize_json +@param data The data to serialize +@param writer A function to write a chunk of JSON data (optional) +@return String containing the JSON if called without write callback +]] + +---[[ +Returns the absolute path to LuCI base directory. + +@class function +@name libpath +@return String containing the directory path +]] + +---[[ +This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function + +@class function +@name coxpcall +@param f Lua function to be called protected +@param err Custom error handler +@param ... Parameters passed to the function +@return A boolean whether the function call succeeded and the return +-- values of either the function or the error handler +]] + +---[[ +This is a coroutine-safe drop-in replacement for Lua's "pcall"-function + +@class function +@name copcall +@param f Lua function to be called protected +@param ... Parameters passed to the function +@return A boolean whether the function call succeeded and the returns +-- values of the function or the error object +]] + |