Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 0x4A6F-master
  • 0x4A6F-rpi4
  • autinerd/experimental-openwrt-24.10
  • experimental
  • feature/addMikrotikwAP
  • master
  • nrb/airmax-test
  • nrb/ar9344-reset-sequence
  • nrb/ex400-remove-wps
  • nrb/gluon-master-cpe510
  • nrb/test-radv-filter
  • nrbffs/fastd-remove-delay
  • nrbffs/netgear-ex6120
  • v2018.2.2-ffs
  • v2018.2.3-ffs
  • v2019.1-ffs
  • v2019.1.1-ffs
  • v2019.1.2-ffs
  • v2020.1-ffs
  • v2020.1.1-ffs
  • v2020.1.3-ffs
  • v2020.2-ffs
  • v2020.2.1-ffs
  • v2020.2.2-ffs
  • v2020.2.3-ffs
  • v2021.1-ffs
  • v2021.1.1-ffs
  • v2021.1.2-ffs
  • v2022.1.1-ffs
  • v2022.1.3-ffs
  • v2022.1.4-ffs
  • v2023.1-ffs
  • v2023.2-ffs
  • v2023.2.2-ffs
  • v2023.2.3-ffs
  • v2023.2.4-ffs
  • v2023.2.5-ffs
  • experimental-2022-09-24
  • experimental-2022-09-24-base
  • experimental-2023-03-11
  • experimental-2023-03-11-base
  • experimental-2023-03-12
  • experimental-2023-03-12-base
  • experimental-2023-03-16
  • experimental-2023-03-16-base
  • experimental-2023-03-20
  • experimental-2023-03-20-base
  • experimental-2023-03-23
  • experimental-2023-03-23-base
  • experimental-2023-03-25
  • experimental-2023-03-25-base
  • experimental-2023-03-26
  • experimental-2023-03-26-base
  • experimental-2023-03-30
  • experimental-2023-03-30-base
  • experimental-2023-03-31
  • experimental-2023-03-31-base
  • experimental-2023-04-01
  • experimental-2023-04-01-base
  • experimental-2023-04-08
  • experimental-2023-04-08-base
  • experimental-2023-04-10
  • experimental-2023-04-10-base
  • experimental-2023-04-13
  • experimental-2023-04-13-base
  • experimental-2023-04-15
  • experimental-2023-04-15-base
  • experimental-2023-04-16
  • experimental-2023-04-16-base
  • experimental-2023-04-18
  • experimental-2023-04-18-base
  • experimental-2023-04-20
  • experimental-2023-04-20-base
  • experimental-2023-04-26
  • experimental-2023-04-26-base
  • experimental-2023-04-28
  • experimental-2023-04-28-base
  • experimental-2023-04-30
  • experimental-2023-04-30-base
  • experimental-2023-05-02
  • experimental-2023-05-02-base
  • experimental-2023-05-03
  • experimental-2023-05-03-base
  • experimental-2023-05-12
  • experimental-2023-05-12-base
  • experimental-2023-05-21
  • experimental-2023-05-21-base
  • experimental-2023-05-25
  • experimental-2023-05-25-base
  • experimental-2023-07-02
  • experimental-2023-07-02-base
  • experimental-2023-07-04
  • experimental-2023-07-04-base
  • experimental-2023-07-12
  • experimental-2023-07-12-base
  • experimental-2023-07-16
  • experimental-2023-07-16-base
  • experimental-2023-08-04
  • experimental-2023-08-04-base
  • experimental-2023-08-10
  • experimental-2023-08-10-base
  • experimental-2023-09-08
  • experimental-2023-09-08-base
  • experimental-2023-09-09
  • experimental-2023-09-09-base
  • experimental-2023-09-10
  • experimental-2023-09-10-base
  • experimental-2023-09-11
  • experimental-2023-09-11-base
  • experimental-2023-09-12
  • experimental-2023-09-12-base
  • experimental-2023-09-13
  • experimental-2023-09-13-base
  • experimental-2023-09-15
  • experimental-2023-09-15-base
  • experimental-2023-09-16
  • experimental-2023-09-16-base
  • experimental-2023-09-18
  • experimental-2023-09-18-base
  • experimental-2023-09-20
  • experimental-2023-09-20-base
  • experimental-2023-09-27
  • experimental-2023-09-27-base
  • experimental-2023-09-28
  • experimental-2023-09-28-base
  • experimental-2023-09-29
  • experimental-2023-09-29-base
  • experimental-2023-10-02
  • experimental-2023-10-02-base
  • experimental-2023-10-13
  • experimental-2023-10-13-base
  • experimental-2023-10-14
  • experimental-2023-10-14-base
  • experimental-2023-10-16
  • experimental-2023-10-16-base
  • experimental-2023-10-23
  • experimental-2023-10-23-base
137 results

Target

Select target project
  • firmware/gluon
  • 0x4A6F/gluon
  • patrick/gluon
3 results
Select Git revision
  • 0x4A6F-master
  • 0x4A6F-rpi4
  • 2014.3.x
  • 2014.4.x
  • babel
  • hoodselector
  • master
  • radv-filterd
  • v2015.1.x
  • v2016.1.x
  • v2016.2.4-batmanbug
  • v2016.2.x
  • v2018.2.2-ffs
  • v2018.2.x
  • v2014.1
  • v2014.2
  • v2014.3
  • v2014.3.1
  • v2014.4
  • v2015.1
  • v2015.1.1
  • v2015.1.2
  • v2016.1
  • v2016.1.1
  • v2016.1.2
  • v2016.1.3
  • v2016.1.4
  • v2016.1.5
  • v2016.1.6
  • v2016.2
  • v2016.2.1
  • v2016.2.2
  • v2016.2.3
  • v2016.2.4
  • v2016.2.5
  • v2016.2.6
  • v2016.2.7
  • v2017.1
  • v2017.1.1
  • v2017.1.2
  • v2017.1.3
  • v2017.1.4
  • v2017.1.5
  • v2017.1.6
  • v2017.1.7
  • v2017.1.8
  • v2018.1
  • v2018.1.1
  • v2018.1.2
  • v2018.1.3
  • v2018.1.4
  • v2018.2
  • v2018.2-ffs0.1
  • v2018.2.1
  • v2018.2.1-ffs0.1
  • v2018.2.2-ffs0.1
56 results
Show changes
Showing
with 749 additions and 723 deletions
<fieldset class="gluon-section">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
<% if self.description and #self.description > 0 then -%>
<div class="gluon-section-descr"><%=self.description%></div>
<%- end %>
<div class="gluon-section-node">
<div id="section-<%=id%>">
<% self:render_children(renderer, scope) %>
</div>
<% if self.error and self.error[1] then -%>
<div class="gluon-section-error">
<ul><% for _, e in ipairs(self.error[1]) do -%>
<li>
<%- if e == "invalid" then -%>
<%:One or more fields contain invalid values!%>
<%- elseif e == "missing" then -%>
<%:One or more required fields have no value!%>
<%- else -%>
<%=pcdata(e)%>
<%- end -%>
</li>
<%- end %></ul>
</div>
<%- end %>
</div>
</fieldset>
<textarea class="gluon-input-textarea" <% if not self.size then %> style="width: 100%"<% else %> cols="<%=self.size%>"<% end %> data-update="change"<%= attr("name", id) .. attr("id", id) .. attr("rows", self.rows) .. attr("wrap", self.wrap) %>>
<%-=pcdata(self:cfgvalue())-%>
</textarea>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="refresh" content="0; URL=/cgi-bin/gluon" />
</head>
<body>
</body>
</html>
!function(){function e(e){return/^-?\d+$/.test(e)?+e:NaN}function t(e){return/^-?\d*\.?\d+?$/.test(e)?+e:NaN}function n(e){var t,n;return(n=e.match(/^([^\(]+)\(([^,]+),([^\)]+)\)$/))&&(t=s[n[1]])!==undefined?function(){return t.apply(this,[n[2],n[3]])}:(n=e.match(/^([^\(]+)\(([^,\)]+)\)$/))&&(t=s[n[1]])!==undefined?function(){return t.apply(this,[n[2]])}:s[e]}function a(e,t){var n,a=document.getElementById(e);return a&&(n="checkbox"==a.type?a.checked:a.value?a.value:""),n==t}function r(e){for(var t=0;t<e.length;t++){var n=!0;for(var r in e[t])n=n&&a(r,e[t][r]);if(n)return!0}return!1}function i(){var e=!1;for(var t in c){var n=c[t],a=document.getElementById(t),u=document.getElementById(n.parent);if(a&&a.parentNode&&!r(n.deps))a.parentNode.removeChild(a),e=!0;else if(u&&(!a||!a.parentNode)&&r(n.deps)){var d=undefined;for(d=u.firstChild;d&&!(d.getAttribute&&parseInt(d.getAttribute("data-index"),10)>n.index);d=d.nextSibling);d?u.insertBefore(n.node,d):u.appendChild(n.node),e=!0}u&&u.parentNode&&u.getAttribute("data-optionals")&&(u.parentNode.style.display=u.options.length<=1?"none":"")}e&&i()}function u(e,t,n,a){return e.addEventListener?e.addEventListener(t,n,!!a):e.attachEvent("on"+t,function(){var e=window.event;return!e.target&&e.srcElement&&(e.target=e.srcElement),!!n(e)}),e}function d(e,t,n){function a(a,s,f){for(var p=[];e.firstChild;){var h=e.firstChild,v=+h.index;v!=f&&("input"==h.nodeName.toLowerCase()?p.push(h.value||""):"select"==h.nodeName.toLowerCase()&&(p[p.length-1]=h.options[h.selectedIndex].value)),e.removeChild(h)}s>=0?(a=s+1,p.splice(s,0,"")):n||0!=p.length||p.push("");for(var v=1;v<=p.length;v++){var g=document.createElement("input");if(g.id=l+"."+v,g.name=l,g.value=p[v-1],g.type="text",g.index=v,g.className="gluon-input-text",c&&(g.placeholder=c),e.appendChild(g),t&&o(g,!1,t),u(g,"keydown",i),u(g,"keypress",r),v==a)g.focus();else if(-v==a){g.focus();var m=g.value;g.value=" ",g.value=m}if(n||p.length>1){var y=document.createElement("span");y.className="gluon-remove",e.appendChild(y),u(y,"click",d(!1)),e.appendChild(document.createElement("br"))}}var y=document.createElement("span");y.className="gluon-add",e.appendChild(y),u(y,"click",d(!0))}function r(e){e=e?e:window.event;var t=e.target?e.target:e.srcElement;switch(3==t.nodeType&&(t=t.parentNode),e.keyCode){case 8:case 46:return 0!=t.value.length||(e.preventDefault&&e.preventDefault(),!1);case 13:case 38:case 40:return e.preventDefault&&e.preventDefault(),!1}return!0}function i(e){e=e?e:window.event;var t,n,r=e.target?e.target:e.srcElement,i=0;if(r){for(3==r.nodeType&&(r=r.parentNode),i=r.index,t=r.previousSibling;t&&t.name!=l;)t=t.previousSibling;for(n=r.nextSibling;n&&n.name!=l;)n=n.nextSibling}switch(e.keyCode){case 8:case 46:if("select"==r.nodeName.toLowerCase()||0==r.value.length){e.preventDefault&&e.preventDefault();var u=r.index;return 8==e.keyCode&&(u=1-u),a(u,-1,i),!1}break;case 13:a(-1,i,-1);break;case 38:t&&t.focus();break;case 40:n&&n.focus()}return!0}function d(e){return function(t){t=t?t:window.event;for(var n=t.target?t.target:t.srcElement,a=n.previousSibling;a&&a.name!=l;)a=a.previousSibling;return e?i({target:a,keyCode:13}):(a.value="",i({target:a,keyCode:8})),!1}}var l=e.getAttribute("data-prefix"),c=e.getAttribute("data-placeholder");a(NaN,-1,-1)}function o(e,t,a){var r=n(a);if(r){var i=function(){if(e.form){e.className=e.className.replace(/ gluon-input-invalid/g,"");var n=e.options&&e.options.selectedIndex>-1?e.options[e.options.selectedIndex].value:e.value;0==n.length&&t||r.apply(n)||(e.className+=" gluon-input-invalid")}};u(e,"blur",i),u(e,"keyup",i),"SELECT"==e.nodeName&&(u(e,"change",i),u(e,"click",i)),i()}}function l(e,t,n){var a=c[e.id];a||(a={node:e,parent:e.parentNode.id,deps:[],index:n},c[e.id]=a),a.deps.push(t)}var c={},s={integer:function(){return!isNaN(e(this))},uinteger:function(){return e(this)>=0},"float":function(){return!isNaN(t(this))},ufloat:function(){return t(this)>=0},ipaddr:function(){return s.ip4addr.apply(this)||s.ip6addr.apply(this)},ip4addr:function(){var e;return!!(e=this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))&&(e[1]>=0&&e[1]<=255&&e[2]>=0&&e[2]<=255&&e[3]>=0&&e[3]<=255&&e[4]>=0&&e[4]<=255)},ip6addr:function(){return this.indexOf("::")<0?null!=this.match(/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i):!(this.indexOf(":::")>=0||this.match(/::.+::/)||this.match(/^:[^:]/)||this.match(/[^:]:$/))&&(!!this.match(/^(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}$/i)||(!!this.match(/^(?:[a-f0-9]{1,4}:){7}:$/i)||!!this.match(/^:(?::[a-f0-9]{1,4}){7}$/i)))},wpakey:function(){var e=this;return 64==e.length?null!=e.match(/^[a-f0-9]{64}$/i):e.length>=8&&e.length<=63},range:function(e,n){var a=t(this);return a>=+e&&a<=+n},min:function(e){return t(this)>=+e},max:function(e){return t(this)<=+e},irange:function(t,n){var a=e(this);return a>=+t&&a<=+n},imin:function(t){return e(this)>=+t},imax:function(t){return e(this)<=+t},minlength:function(e){return(""+this).length>=+e},maxlength:function(e){return(""+this).length<=+e}};!function(){var e;e=document.querySelectorAll("[data-depends]");for(var t,n=0;(t=e[n])!==undefined;n++){var a=parseInt(t.getAttribute("data-index"),10),r=JSON.parse(t.getAttribute("data-depends"));if(!isNaN(a)&&r.length>0)for(var c=0;c<r.length;c++)l(t,r[c],a)}e=document.querySelectorAll("[data-update]");for(var t,n=0;(t=e[n])!==undefined;n++)for(var s,f=t.getAttribute("data-update").split(" "),p=0;(s=f[p])!==undefined;p++)u(t,s,i);e=document.querySelectorAll("[data-type]");for(var t,n=0;(t=e[n])!==undefined;n++)o(t,"true"===t.getAttribute("data-optional"),t.getAttribute("data-type"));e=document.querySelectorAll("[data-dynlist]");for(var t,n=0;(t=e[n])!==undefined;n++){var h=JSON.parse(t.getAttribute("data-dynlist"));d(t,h.type,h.optional)}i()}()}();
\ No newline at end of file
......@@ -10,47 +10,15 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Form token mismatch"
msgstr "Formular-Token ungültig"
msgid ""
"In order to prevent unauthorized access to the system, your request has been "
"blocked."
msgstr ""
"Die Anfrage wurde blockiert, um unauthorisierten Zugriff aufs System zu verhindern."
msgid "Internal Server Error"
msgstr "Interner Serverfehler"
msgid "JavaScript required!"
msgstr "JavaScript benötigt!"
msgid "Not Found"
msgstr "Nicht Gefunden"
msgid "One or more fields contain invalid values!"
msgstr "Ein oder mehrere Felder enthalten ungültige Werte!"
msgid "One or more required fields have no value!"
msgstr "Ein oder mehr benötigte Felder sind nicht ausgefüllt!"
msgid "Reset"
msgstr "Zurücksetzen"
msgid "Save"
msgstr "Speichern"
msgid "Sorry, the object you requested was not found."
msgstr "Entschuldigung, das anfgeforderte Objekt wurde nicht gefunden."
msgstr "Entschuldigung, das angeforderte Objekt wurde nicht gefunden."
msgid "Sorry, the server encountered an unexpected error."
msgstr ""
"Entschuldigung, auf dem Server ist ein unerwarteter Fehler aufgetreten."
msgid "The submitted security token is invalid or already expired!"
msgstr "Das übermittelte Sicherheits-Token ist ungültig oder bereits abgelaufen!"
msgid ""
"You must enable JavaScript in your browser or the web interface will not "
"work properly."
msgstr ""
......@@ -10,45 +10,14 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "Form token mismatch"
msgstr ""
msgid ""
"In order to prevent unauthorized access to the system, your request has been "
"blocked."
msgstr ""
msgid "Internal Server Error"
msgstr "Erreur Serveur Interne"
msgid "JavaScript required!"
msgstr ""
msgid "Not Found"
msgstr "Pas trouvé"
msgid "One or more fields contain invalid values!"
msgstr "Un ou plusieurs champs contiennent des valeurs incorrectes !"
msgid "One or more required fields have no value!"
msgstr "Un ou plusieurs champs n'ont pas de valeur !"
msgid "Reset"
msgstr "Remise à zéro"
msgid "Save"
msgstr "Soumettre"
msgid "Sorry, the object you requested was not found."
msgstr "Désolé, l'objet que vous avez demandé n'as pas été trouvé."
msgid "Sorry, the server encountered an unexpected error."
msgstr "Désolé, le serveur à rencontré une erreur inattendue."
msgid "The submitted security token is invalid or already expired!"
msgstr ""
msgid ""
"You must enable JavaScript in your browser or the web interface will not "
"work properly."
msgstr ""
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Form token mismatch"
msgstr ""
msgid ""
"In order to prevent unauthorized access to the system, your request has been "
"blocked."
msgstr ""
msgid "Internal Server Error"
msgstr ""
msgid "JavaScript required!"
msgstr ""
msgid "Not Found"
msgstr ""
msgid "One or more fields contain invalid values!"
msgstr ""
msgid "One or more required fields have no value!"
msgstr ""
msgid "Reset"
msgstr ""
msgid "Save"
msgstr ""
msgid "Sorry, the object you requested was not found."
msgstr ""
msgid "Sorry, the server encountered an unexpected error."
msgstr ""
msgid "The submitted security token is invalid or already expired!"
msgstr ""
msgid ""
"You must enable JavaScript in your browser or the web interface will not "
"work properly."
msgstr ""
#!/usr/bin/lua
require "gluon.web.cgi"
gluon.web.cgi.run()
......@@ -2,10 +2,9 @@
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
module("gluon.web.cgi", package.seeall)
local nixio = require("nixio")
require("gluon.web.http")
require("gluon.web.dispatcher")
local stdlib = require 'posix.stdlib'
local http = require 'gluon.web.http'
local dispatcher = require 'gluon.web.dispatcher'
-- Limited source to avoid endless blocking
local function limitsource(handle, limit)
......@@ -27,12 +26,11 @@ local function limitsource(handle, limit)
end
end
function run()
local http = gluon.web.http.Http(
nixio.getenv(),
limitsource(io.stdin, tonumber(nixio.getenv("CONTENT_LENGTH"))),
return function(config)
local env = stdlib.getenv()
dispatcher(config, http.Http(
env,
limitsource(io.stdin, tonumber(env.CONTENT_LENGTH)),
io.stdout
)
gluon.web.dispatcher.httpdispatch(http)
))
end
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
-- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
local fs = require "nixio.fs"
local glob = require 'posix.glob'
local json = require "jsonc"
local tpl = require "gluon.web.template"
local util = require "gluon.web.util"
local proto = require "gluon.web.http.protocol"
module("gluon.web.dispatcher", package.seeall)
function build_url(http, path)
local function build_url(http, path)
return (http:getenv("SCRIPT_NAME") or "") .. "/" .. table.concat(path, "/")
end
function redirect(http, ...)
http:redirect(build_url(http, {...}))
end
function node_visible(node)
return (
node.title and
node.target and
(not node.hidden)
)
end
function node_children(node)
if not node then return {} end
local ret = {}
for k, v in pairs(node.nodes) do
if node_visible(v) then
table.insert(ret, k)
end
end
table.sort(ret,
function(a, b)
return (node.nodes[a].order or 100)
< (node.nodes[b].order or 100)
end
)
return ret
end
function httpdispatch(http)
local request = {}
local pathinfo = proto.urldecode(http:getenv("PATH_INFO") or "", true)
for node in pathinfo:gmatch("[^/]+") do
table.insert(request, node)
end
ok, err = pcall(dispatch, http, request)
if not ok then
http:status(500, "Internal Server Error")
http:prepare_content("text/plain")
http:write(err)
end
end
local function set_language(renderer, accept)
local langs = {}
......@@ -76,7 +28,7 @@ local function set_language(renderer, accept)
end
for match in accept:gmatch("[^,]+") do
local lang = match:match('^%s*([^%s;-_]+)')
local lang = match:match('^%s*([^%s;_-]+)')
local q = tonumber(match:match(';q=(%S+)%s*$') or 1)
if lang == '*' then
......@@ -92,15 +44,10 @@ local function set_language(renderer, accept)
return (weights[a] or 0) > (weights[b] or 0)
end)
for _, lang in ipairs(langs) do
if renderer.setlanguage(lang) then
return
end
end
renderer.set_language(langs)
end
function dispatch(http, request)
local function dispatch(config, http, request)
local tree = {nodes={}}
local nodes = {[''] = tree}
......@@ -126,37 +73,49 @@ function dispatch(http, request)
end
if type(val) == "table" then
val = util.serialize_json(val)
val = json.stringify(val)
end
return string.format(' %s="%s"', key, util.pcdata(tostring(val)))
end
local renderer = tpl.renderer(setmetatable({
local renderer = tpl(config, setmetatable({
http = http,
request = request,
node = function(path) return _node({path}) end,
write = function(...) return http:write(...) end,
pcdata = util.pcdata,
urlencode = proto.urlencode,
media = '/static/gluon',
theme = 'gluon',
resource = '/static/resources',
attr = attr,
json = json.stringify,
url = function(path) return build_url(http, path) end,
}, { __index = _G }))
local function createtree()
local base = config.base_path .. "/controller/"
local function load_ctl(path)
local ctl = assert(loadfile(path))
local _pkg
local subdisp = setmetatable({
package = function(name)
_pkg = name
end,
node = function(...)
return _node({...})
end,
entry = function(path, target, title, order)
local c = _node(path, true)
entry = function(entry_path, target, title, order)
local c = _node(entry_path, true)
c.target = target
c.title = title
c.order = order
c.pkg = _pkg
return c
end,
......@@ -175,32 +134,17 @@ function dispatch(http, request)
end
end,
template = function(view)
template = function(view, scope)
local pkg = _pkg
return function()
renderer.render("layout", {content = view})
renderer.render_layout(view, scope, pkg)
end
end,
model = function(name)
local pkg = _pkg
return function()
local hidenav = false
local model = require "gluon.web.model"
local maps = model.load(name, renderer)
for _, map in ipairs(maps) do
map:parse(http)
end
for _, map in ipairs(maps) do
map:handle()
hidenav = hidenav or map.hidenav
end
renderer.render("layout", {
content = "model/wrapper",
maps = maps,
hidenav = hidenav,
})
require('gluon.web.model')(config, http, renderer, name, pkg)
end
end,
......@@ -209,22 +153,16 @@ function dispatch(http, request)
end,
}, { __index = _G })
local function createtree()
local base = util.libpath() .. "/controller/"
local function load_ctl(path)
local ctl = assert(loadfile(path))
local env = setmetatable({}, { __index = subdisp })
setfenv(ctl, env)
ctl()
end
for path in (fs.glob(base .. "*.lua") or function() end) do
for _, path in ipairs(glob.glob(base .. "*.lua", 0) or {}) do
load_ctl(path)
end
for path in (fs.glob(base .. "*/*.lua") or function() end) do
for _, path in ipairs(glob.glob(base .. "*/*.lua", 0) or {}) do
load_ctl(path)
end
end
......@@ -238,21 +176,44 @@ function dispatch(http, request)
if not node or not node.target then
http:status(404, "Not Found")
renderer.render("layout", { content = "error404", message =
renderer.render_layout("error/404", {
message =
"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
"If this URL belongs to an extension, make sure it is properly installed.\n"
})
"If this URL belongs to an extension, make sure it is properly installed.\n",
}, 'gluon-web')
return
end
http:parse_input(node.filehandler)
local ok, err = pcall(http.parse_input, http, node.filehandler)
if not ok then
http:status(400, "Bad request")
http:prepare_content("text/plain")
http:write(err .. "\r\n")
return
end
local ok, err = pcall(node.target)
ok, err = pcall(node.target)
if not ok then
http:status(500, "Internal Server Error")
renderer.render("layout", { content = "error500", message =
renderer.render_layout("error/500", {
message =
"Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)")
})
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"),
}, 'gluon-web')
end
end
return function(config, http)
local request = {}
local pathinfo = proto.urldecode(http:getenv("PATH_INFO") or "", true)
for node in pathinfo:gmatch("[^/]+") do
table.insert(request, node)
end
local ok, err = pcall(dispatch, config, http, request)
if not ok then
http:status(500, "Internal Server Error")
http:prepare_content("text/plain")
http:write(err .. "\r\n")
end
end
......@@ -2,18 +2,15 @@
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
local string = string
local table = table
local nixio = require "nixio"
local protocol = require "gluon.web.http.protocol"
local util = require "gluon.web.util"
local ipairs, pairs, tostring = ipairs, pairs, tostring
module "gluon.web.http"
local M = {}
local Http = util.class()
M.Http = Http
Http = util.class()
function Http:__init__(env, input, output)
self.input = input
self.output = output
......@@ -56,8 +53,8 @@ end
function Http:getcookie(name)
local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
local p = ";" .. name .. "=(.-);"
local i, j, value = c:find(p)
return value and urldecode(value)
local _, _, value = c:find(p)
return value and protocol.urldecode(value)
end
function Http:getenv(name)
......@@ -81,13 +78,6 @@ end
function Http:prepare_content(mime)
if self.headers["content-type"] then return end
if mime == "application/xhtml+xml" then
local accept = self:getenv("HTTP_ACCEPT")
if not accept or not accept:find("application/xhtml+xml", nil, true) then
mime = "text/html; charset=UTF-8"
end
self:header("Vary", "Accept")
end
self:header("Content-Type", mime)
end
......@@ -121,3 +111,5 @@ function Http:redirect(url)
self:header("Location", url)
self:close()
end
return M
......@@ -3,12 +3,9 @@
-- Licensed to the public under the Apache License 2.0.
-- This class contains several functions useful for http message- and content
-- decoding and to retrive form data from raw http messages.
module("gluon.web.http.protocol", package.seeall)
HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size
-- decoding and to retrieve form data from raw http messages.
local M = {}
local function pump(src, snk)
while true do
......@@ -26,7 +23,7 @@ local function pump(src, snk)
end
end
function urlencode(s)
function M.urlencode(s)
return (string.gsub(s, '[^a-zA-Z0-9%-_%.~]',
function(c)
local ret = ''
......@@ -41,7 +38,7 @@ function urlencode(s)
end
-- the "+" sign to " " - and return the decoded string.
function urldecode(str, no_plus)
function M.urldecode(str, no_plus)
local function chrdec(hex)
return string.char(tonumber(hex, 16))
......@@ -75,7 +72,7 @@ end
-- 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)
function M.urldecode_params(url)
local params = {}
if url:find("?") then
......@@ -85,8 +82,8 @@ function urldecode_params(url)
for pair in url:gmatch("[^&;]+") do
-- find key and value
local key = urldecode(pair:match("^([^=]+)"))
local val = urldecode(pair:match("^[^=]+=(.+)$"))
local key = M.urldecode(pair:match("^([^=]+)"))
local val = M.urldecode(pair:match("^[^=]+=(.+)$"))
-- store
if key and key:len() > 0 then
......@@ -101,39 +98,34 @@ function urldecode_params(url)
end
-- Content-Type. Stores all extracted data associated with its parameter name
-- in the params table withing the given message object. Multiple parameter
-- 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 feeded with the
-- 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 wheather the current chunk is the last one (eof)
function mimedecode_message_body(src, msg, filecb)
if msg and msg.env.CONTENT_TYPE then
msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$")
-- o Boolean which indicates whether the current chunk is the last one (eof)
local function mimedecode_message_body(src, msg, filecb)
local mime_boundary = (msg.env.CONTENT_TYPE or ''):match("^multipart/form%-data; boundary=(.+)$")
if not mime_boundary then
error("Invalid Content-Type found")
end
if not msg.mime_boundary then
return nil, "Invalid Content-Type found"
end
local tlen = 0
local inhdr = false
local field = nil
local store = nil
local lchunk = nil
local function parse_headers(chunk, field)
local function parse_headers(chunk, pfield)
local stat
repeat
chunk, stat = chunk:gsub(
"^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n",
function(k,v)
field.headers[k] = v
pfield.headers[k] = v
return ""
end
)
......@@ -143,26 +135,26 @@ function mimedecode_message_body(src, msg, filecb)
-- End of headers
if stat > 0 then
if field.headers["Content-Disposition"] then
if field.headers["Content-Disposition"]:match("^form%-data; ") then
field.name = field.headers["Content-Disposition"]:match('name="(.-)"')
field.file = field.headers["Content-Disposition"]:match('filename="(.+)"$')
if pfield.headers["Content-Disposition"] then
if pfield.headers["Content-Disposition"]:match("^form%-data; ") then
pfield.name = pfield.headers["Content-Disposition"]:match('name="(.-)"')
pfield.file = pfield.headers["Content-Disposition"]:match('filename="(.+)"$')
end
end
if not field.headers["Content-Type"] then
field.headers["Content-Type"] = "text/plain"
if not pfield.headers["Content-Type"] then
pfield.headers["Content-Type"] = "text/plain"
end
if field.name then
initval(msg.params, field.name)
if field.file then
appendval(msg.params, field.name, field.file)
if pfield.name then
initval(msg.params, pfield.name)
if pfield.file then
appendval(msg.params, pfield.name, pfield.file)
store = filecb
else
store = function(hdr, buf, eof)
appendval(msg.params, field.name, buf)
store = function(_, buf, _)
appendval(msg.params, pfield.name, buf)
end
end
else
......@@ -191,15 +183,16 @@ function mimedecode_message_body(src, msg, filecb)
local spos, epos, found
repeat
spos, epos = data:find("\r\n--" .. msg.mime_boundary .. "\r\n", 1, true)
spos, epos = data:find("\r\n--" .. mime_boundary .. "\r\n", 1, true)
if not spos then
spos, epos = data:find("\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true)
spos, epos = data:find("\r\n--" .. mime_boundary .. "--\r\n", 1, true)
end
if spos then
local predata = data:sub(1, spos - 1)
local eof
if inhdr then
predata, eof = parse_headers(predata, field)
......@@ -228,11 +221,12 @@ function mimedecode_message_body(src, msg, filecb)
-- We found at least some boundary. Save
-- the unparsed remaining data for the
-- next chunk.
lchunk, data = data, nil
lchunk = data
else
-- There was a complete chunk without a boundary. Parse it as headers or
-- append it as data, depending on our current state.
if inhdr then
local eof
lchunk, eof = parse_headers(data, field)
inhdr = not eof
else
......@@ -243,7 +237,7 @@ function mimedecode_message_body(src, msg, filecb)
if store then
store(field, lchunk, false)
end
lchunk, chunk = chunk, nil
lchunk = chunk
end
end
end
......@@ -251,18 +245,61 @@ function mimedecode_message_body(src, msg, filecb)
return true
end
return pump(src, snk)
assert(pump(src, snk))
end
local function check_post_origin(msg)
local default_port = '80'
local request_scheme = 'http'
if msg.env.HTTPS then
default_port = '443'
request_scheme = 'https'
end
local request_host = msg.env.HTTP_HOST
if not request_host then
error('POST request without Host header')
end
if not request_host:match(':[0-9]+$') then
request_host = request_host .. ':' .. default_port
end
local origin = msg.env.HTTP_ORIGIN
if not origin then
error('POST request without Origin header')
end
local origin_scheme, origin_host = origin:match('^([^:]*)://(.*)$')
if not origin_host then
error('POST request with invalid Origin header')
end
if not origin_host:match(':[0-9]+$') then
local origin_port
if origin_scheme == 'http' then
origin_port = '80'
elseif origin_scheme == 'https' then
origin_port = '443'
else
error('POST request with invalid Origin header')
end
origin_host = origin_host .. ':' .. origin_port
end
if request_scheme ~= origin_scheme or request_host ~= origin_host then
error('Invalid cross-origin POST')
end
end
-- This function will examine the Content-Type within the given message object
-- to select the appropriate content decoder.
-- Currently only the multipart/form-data mime type is supported.
function parse_message_body(src, msg, filecb)
if not (msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE) then
function M.parse_message_body(src, msg, filecb)
if msg.env.REQUEST_METHOD ~= "POST" then
return
end
if msg.env.CONTENT_TYPE:match("^multipart/form%-data") then
return mimedecode_message_body(src, msg, filecb)
end
check_post_origin(msg)
mimedecode_message_body(src, msg, filecb)
end
return M
-- Copyright 2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
local tparser = require 'gluon.web.template.parser'
local unistd = require 'posix.unistd'
return function(config)
local i18ndir = config.base_path .. "/i18n"
local function i18n_file(lang, pkg)
return string.format('%s/%s.%s.lmo', i18ndir, pkg, lang)
end
local function no_translation()
return nil
end
local function load_catalog(lang, pkg)
if pkg then
local file = i18n_file(lang, pkg)
local cat = unistd.access(file) and tparser.load_catalog(file)
if cat then return cat end
end
return no_translation
end
local i18n = {}
function i18n.supported(lang)
return lang == 'en' or unistd.access(i18n_file(lang, 'gluon-web'))
end
function i18n.load(lang, pkg)
local _translate = load_catalog(lang, pkg)
local function translate(key)
return _translate(key) or key
end
local function translatef(key, ...)
return translate(key):format(...)
end
return {
_translate = _translate,
translate = translate,
translatef = translatef,
}
end
return i18n
end
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
-- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
local tparser = require "gluon.web.template.parser"
local util = require "gluon.web.util"
local fs = require "nixio.fs"
local tparser = require 'gluon.web.template.parser'
local tostring, setmetatable, setfenv, pcall, assert = tostring, setmetatable, setfenv, pcall, assert
local tostring, ipairs, setmetatable, setfenv = tostring, ipairs, setmetatable, setfenv
local pcall, assert = pcall, assert
module "gluon.web.template"
return function(config, env)
local i18n = require('gluon.web.i18n')(config)
local viewdir = util.libpath() .. "/view/"
local i18ndir = util.libpath() .. "/i18n/"
local viewdir = config.base_path .. '/view/'
function renderer(env)
local ctx = {}
local language = 'en'
local catalogs = {}
local function render_template(name, template, scope)
function ctx.set_language(langs)
for _, lang in ipairs(langs) do
if i18n.supported(lang) then
language = lang
catalogs = {}
return
end
end
end
function ctx.i18n(pkg)
local cat = catalogs[pkg] or i18n.load(language, pkg)
if pkg then catalogs[pkg] = cat end
return cat
end
local function render_template(name, template, scope, pkg)
scope = scope or {}
local t = ctx.i18n(pkg)
local locals = {
renderer = ctx,
translate = ctx.translate,
translatef = ctx.translatef,
_translate = ctx._translate,
include = function(name)
ctx.render(name, scope)
i18n = ctx.i18n,
translate = t.translate,
translatef = t.translatef,
_translate = t._translate,
include = function(include_name)
ctx.render(include_name, scope, pkg)
end,
}
setfenv(template, setmetatable({}, {
__index = function(tbl, key)
return scope[key] or env[key] or locals[key]
__index = function(_, key)
return scope[key] or locals[key] or env[key]
end
}))
-- Now finally render the thing
local stat, err = pcall(template)
assert(stat, "Failed to execute template '" .. name .. "'.\n" ..
"A runtime error occured: " .. tostring(err or "(nil)"))
"A runtime error occurred: " .. tostring(err or "(nil)"))
end
--- Render a certain template.
-- @param name Template name
-- @param scope Scope to assign to template (optional)
function ctx.render(name, scope)
-- @param pkg i18n namespace package (optional)
function ctx.render(name, scope, pkg)
local sourcefile = viewdir .. name .. ".html"
local template, _, err = tparser.parse(sourcefile)
......@@ -54,45 +73,35 @@ function renderer(env)
"Error while parsing template '" .. sourcefile .. "':\n" ..
(err or "Unknown syntax error"))
render_template(name, template, scope)
render_template(name, template, scope, pkg)
end
--- Render a template from a string.
-- @param template Template string
-- @param scope Scope to assign to template (optional)
function ctx.render_string(str, scope)
-- @param pkg i18n namespace package (optional)
function ctx.render_string(str, scope, pkg)
local template, _, err = tparser.parse_string(str)
assert(template, "Error while parsing template:\n" ..
(err or "Unknown syntax error"))
render_template('(local)', template, scope)
end
function ctx.setlanguage(lang)
lang = lang:gsub("_", "-")
if not lang then return false end
if lang ~= 'en' and not fs.access(i18ndir .. "gluon-web." .. lang .. ".lmo") then
return false
render_template('(local)', template, scope, pkg)
end
return tparser.load_catalog(lang, i18ndir)
end
-- Returns a translated string, or nil if none is found
function ctx._translate(key)
return (tparser.translate(key))
end
-- Returns a translated string, or the original string if none is found
function ctx.translate(key)
return tparser.translate(key) or key
end
function ctx.translatef(key, ...)
local t = ctx.translate(key)
return t:format(...)
--- Render a template, wrapped in the configured layout.
-- @param name Template name
-- @param scope Scope to assign to template (optional)
-- @param pkg i18n namespace package (optional)
-- @param layout_scope Additional variables to pass to the layout template
function ctx.render_layout(name, scope, pkg, layout_scope)
ctx.render(config.layout_template, setmetatable({
content = name,
scope = scope,
pkg = pkg,
}, {
__index = layout_scope
}), config.layout_package)
end
return ctx
......
......@@ -2,17 +2,10 @@
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
-- Licensed to the public under the Apache License 2.0.
local io = require "io"
local table = require "table"
local tparser = require "gluon.web.template.parser"
local json = require "luci.jsonc"
local nixio = require "nixio"
local fs = require "nixio.fs"
local getmetatable, setmetatable = getmetatable, setmetatable
local tostring, pairs = tostring, pairs
module "gluon.web.util"
local M = {}
--
-- Class helper routines
......@@ -38,14 +31,14 @@ end
-- 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)
function M.class(base)
return setmetatable({}, {
__call = _instantiate,
__index = base
})
end
function instanceof(object, class)
function M.instanceof(object, class)
while object do
if object == class then
return true
......@@ -61,40 +54,8 @@ end
-- String and data manipulation routines
--
function pcdata(value)
function M.pcdata(value)
return value and tparser.pcdata(tostring(value))
end
function contains(table, value)
for k, v in pairs(table) do
if value == v then
return k
end
end
return false
end
--
-- System utility functions
--
function exec(command)
local pp = io.popen(command)
local data = pp:read("*a")
pp:close()
return data
end
function uniqueid(bytes)
local rand = fs.readfile("/dev/urandom", bytes)
return nixio.bin.hexlify(rand)
end
serialize_json = json.stringify
function libpath()
return '/lib/gluon/web'
end
return M
all: compile
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -c -o $@ $<
$(CC) $(CPPFLAGS) $(CFLAGS) -D_GNU_SOURCE -std=c99 -Wall -Wextra -fPIC -fvisibility=hidden -c -o $@ $<
clean:
rm -f parser.so *.o
......@@ -9,6 +9,8 @@ clean:
parser.so: template_parser.o template_utils.o template_lmo.o template_lualib.o
$(CC) $(LDFLAGS) -shared -o $@ $^
gluon-po2lmo: gluon-po2lmo.o template_lmo.o
compile: parser.so
install: compile
......
/*
* lmo - Lua Machine Objects - PO to LMO conversion tool
*
* Copyright (C) 2009-2012 Jo-Philipp Wich <jow@openwrt.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "template_lmo.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
__attribute__((noreturn))
static void die(const char *msg)
{
fprintf(stderr, "Error: %s\n", msg);
exit(1);
}
__attribute__((noreturn))
static void usage(const char *name)
{
fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
exit(1);
}
static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
if( fwrite(ptr, size, nmemb, stream) == 0 )
die("Failed to write stdout");
}
static ssize_t extract_string(const char *src, char *dest, size_t len)
{
size_t pos = 0;
int esc = 0;
int off = -1;
for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
{
if( (off == -1) && (src[pos] == '"') )
{
off = pos + 1;
}
else if( off >= 0 )
{
if( esc == 1 )
{
switch (src[pos])
{
case '"':
case '\\':
off++;
break;
}
dest[pos-off] = src[pos];
esc = 0;
}
else if( src[pos] == '\\' )
{
dest[pos-off] = src[pos];
esc = 1;
}
else if( src[pos] != '"' )
{
dest[pos-off] = src[pos];
}
else
{
dest[pos-off] = '\0';
break;
}
}
}
return (off > -1) ? (ssize_t) strlen(dest) : -1;
}
static int cmp_index(const void *a, const void *b)
{
uint32_t x = ((const lmo_entry_t *)a)->key_id;
uint32_t y = ((const lmo_entry_t *)b)->key_id;
if (x < y)
return -1;
else if (x > y)
return 1;
return 0;
}
static void print_uint32(uint32_t x, FILE *out)
{
uint32_t y = htonl(x);
print(&y, sizeof(uint32_t), 1, out);
}
static void print_index(void *array, int n, FILE *out)
{
lmo_entry_t *e;
qsort(array, n, sizeof(*e), cmp_index);
for (e = array; n > 0; n--, e++)
{
print_uint32(e->key_id, out);
print_uint32(e->val_id, out);
print_uint32(e->offset, out);
print_uint32(e->length, out);
}
}
int main(int argc, char *argv[])
{
char line[4096];
char key[4096];
char val[4096];
char tmp[4096];
int state = 0;
int offset = 0;
int length = 0;
int n_entries = 0;
void *array = NULL;
lmo_entry_t *entry = NULL;
uint32_t key_id, val_id;
FILE *in;
FILE *out;
if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
usage(argv[0]);
memset(line, 0, sizeof(key));
memset(key, 0, sizeof(val));
memset(val, 0, sizeof(val));
while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
{
if( state == 0 && strstr(line, "msgid \"") == line )
{
switch(extract_string(line, key, sizeof(key)))
{
case -1:
die("Syntax error in msgid");
case 0:
state = 1;
break;
default:
state = 2;
}
}
else if( state == 1 || state == 2 )
{
if( strstr(line, "msgstr \"") == line || state == 2 )
{
switch(extract_string(line, val, sizeof(val)))
{
case -1:
state = 4;
break;
default:
state = 3;
}
}
else
{
switch(extract_string(line, tmp, sizeof(tmp)))
{
case -1:
state = 2;
break;
default:
strcat(key, tmp);
}
}
}
else if( state == 3 )
{
switch(extract_string(line, tmp, sizeof(tmp)))
{
case -1:
state = 4;
break;
default:
strcat(val, tmp);
}
}
if( state == 4 )
{
if( strlen(key) > 0 && strlen(val) > 0 )
{
key_id = sfh_hash(key, strlen(key));
val_id = sfh_hash(val, strlen(val));
if( key_id != val_id )
{
n_entries++;
array = realloc(array, n_entries * sizeof(lmo_entry_t));
entry = (lmo_entry_t *)array + n_entries - 1;
if (!array)
die("Out of memory");
entry->key_id = key_id;
entry->val_id = val_id;
entry->offset = offset;
entry->length = strlen(val);
length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
print(val, length, 1, out);
offset += length;
}
}
state = 0;
memset(key, 0, sizeof(key));
memset(val, 0, sizeof(val));
}
memset(line, 0, sizeof(line));
}
print_index(array, n_entries, out);
if( offset > 0 )
{
print_uint32(offset, out);
fsync(fileno(out));
fclose(out);
}
else
{
fclose(out);
unlink(argv[2]);
}
fclose(in);
return(0);
}
......@@ -2,6 +2,7 @@
* lmo - Lua Machine Objects - Base functions
*
* Copyright (C) 2009-2010 Jo-Philipp Wich <jow@openwrt.org>
* Copyright (C) 2018 Matthias Schiffer <mschiffer@universe-factory.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -18,38 +19,56 @@
#include "template_lmo.h"
#include <sys/stat.h>
#include <sys/mman.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline uint16_t get_le16(const void *data) {
const uint8_t *d = data;
return (((uint16_t)d[1]) << 8) | d[0];
}
static inline uint32_t get_be32(const void *data) {
const uint8_t *d = data;
return (((uint32_t)d[0]) << 24)
| (((uint32_t)d[1]) << 16)
| (((uint32_t)d[2]) << 8)
| d[3];
}
/*
* Hash function from http://www.azillionmonkeys.com/qed/hash.html
* Copyright (C) 2004-2008 by Paul Hsieh
*/
static uint32_t sfh_hash(const char *data, int len)
uint32_t sfh_hash(const void *input, size_t len)
{
const uint8_t *data = input;
uint32_t hash = len, tmp;
int rem;
if (len <= 0 || data == NULL) return 0;
rem = len & 3;
len >>= 2;
/* Main loop */
for (;len > 0; len--) {
hash += sfh_get16(data);
tmp = (sfh_get16(data+2) << 11) ^ hash;
for (; len > 3; len -= 4) {
hash += get_le16(data);
tmp = (get_le16(data+2) << 11) ^ hash;
hash = (hash << 16) ^ tmp;
data += 2*sizeof(uint16_t);
data += 4;
hash += hash >> 11;
}
/* Handle end cases */
switch (rem) {
case 3: hash += sfh_get16(data);
switch (len) {
case 3: hash += get_le16(data);
hash ^= hash << 16;
hash ^= data[sizeof(uint16_t)] << 18;
hash ^= data[2] << 18;
hash += hash >> 11;
break;
case 2: hash += sfh_get16(data);
case 2: hash += get_le16(data);
hash ^= hash << 11;
hash += hash >> 17;
break;
......@@ -69,220 +88,90 @@ static uint32_t sfh_hash(const char *data, int len)
return hash;
}
static uint32_t lmo_canon_hash(const char *str, int len)
{
char res[4096];
char *ptr, prev;
int off;
if (!str || len >= sizeof(res))
return 0;
for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
{
if (isspace(*str))
{
if (!isspace(prev))
*ptr++ = ' ';
}
else
{
*ptr++ = *str;
}
}
if ((ptr > res) && isspace(*(ptr-1)))
ptr--;
return sfh_hash(res, ptr - res);
}
static lmo_archive_t * lmo_open(const char *file)
bool lmo_load(lmo_catalog_t *cat, const char *file)
{
int in = -1;
uint32_t idx_offset = 0;
int fd = -1;
struct stat s;
lmo_archive_t *ar = NULL;
cat->data = MAP_FAILED;
if (stat(file, &s) == -1)
fd = open(file, O_RDONLY|O_CLOEXEC);
if (fd < 0)
goto err;
if ((in = open(file, O_RDONLY)) == -1)
if (fstat(fd, &s))
goto err;
if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
{
memset(ar, 0, sizeof(*ar));
ar->fd = in;
ar->size = s.st_size;
cat->data = mmap(NULL, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
close(fd);
fd = -1;
if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
if (cat->data == MAP_FAILED)
goto err;
idx_offset = ntohl(*((const uint32_t *)
(ar->mmap + ar->size - sizeof(uint32_t))));
cat->end = cat->data + s.st_size;
uint32_t idx_offset = get_be32(cat->end - sizeof(uint32_t));
cat->index = (const lmo_entry_t *)(cat->data + idx_offset);
if (idx_offset >= ar->size)
if ((const char *)cat->index > (cat->end - sizeof(uint32_t)))
goto err;
ar->index = (lmo_entry_t *)(ar->mmap + idx_offset);
ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
ar->end = ar->mmap + ar->size;
cat->length = (cat->end - sizeof(uint32_t) - (const char *)cat->index) / sizeof(lmo_entry_t);
return ar;
}
return true;
err:
if (in > -1)
close(in);
if (fd >= 0)
close(fd);
if (ar != NULL)
{
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
munmap(ar->mmap, ar->size);
free(ar);
}
if (cat->data != MAP_FAILED)
munmap(cat->data, cat->end - cat->data);
return NULL;
return false;
}
static lmo_catalog_t *_lmo_catalogs;
static lmo_catalog_t *_lmo_active_catalog;
int lmo_load_catalog(const char *lang, const char *dir)
{
DIR *dh = NULL;
char pattern[16];
char path[PATH_MAX];
struct dirent *de = NULL;
lmo_archive_t *ar = NULL;
lmo_catalog_t *cat = NULL;
if (!lmo_change_catalog(lang))
return 0;
if (!dir || !(dh = opendir(dir)))
goto err;
if (!(cat = malloc(sizeof(*cat))))
goto err;
memset(cat, 0, sizeof(*cat));
snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
while ((de = readdir(dh)) != NULL)
{
if (!fnmatch(pattern, de->d_name, 0))
{
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
ar = lmo_open(path);
if (ar)
void lmo_unload(lmo_catalog_t *cat)
{
ar->next = cat->archives;
cat->archives = ar;
}
if (cat->data != MAP_FAILED)
munmap(cat->data, cat->end - cat->data);
}
}
closedir(dh);
cat->next = _lmo_catalogs;
_lmo_catalogs = cat;
if (!_lmo_active_catalog)
_lmo_active_catalog = cat;
return 0;
err:
if (dh) closedir(dh);
if (cat) free(cat);
return -1;
}
int lmo_change_catalog(const char *lang)
static int lmo_compare_entry(const void *a, const void *b)
{
lmo_catalog_t *cat;
for (cat = _lmo_catalogs; cat; cat = cat->next)
{
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
{
_lmo_active_catalog = cat;
return 0;
}
}
const lmo_entry_t *ea = a, *eb = b;
uint32_t ka = ntohl(ea->key_id), kb = ntohl(eb->key_id);
if (ka < kb)
return -1;
else if (ka > kb)
return 1;
else
return 0;
}
static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
{
unsigned int m, l, r;
uint32_t k;
l = 0;
r = ar->length - 1;
while (1)
{
m = l + ((r - l) / 2);
if (r < l)
break;
k = ntohl(ar->index[m].key_id);
if (k == hash)
return &ar->index[m];
if (k > hash)
static const lmo_entry_t * lmo_find_entry(const lmo_catalog_t *cat, uint32_t hash)
{
if (!m)
break;
lmo_entry_t key;
key.key_id = htonl(hash);
r = m - 1;
}
else
{
l = m + 1;
}
return bsearch(&key, cat->index, cat->length, sizeof(lmo_entry_t), lmo_compare_entry);
}
return NULL;
}
int lmo_translate(const char *key, int keylen, char **out, int *outlen)
bool lmo_translate(const lmo_catalog_t *cat, const char *key, size_t keylen, const char **out, size_t *outlen)
{
uint32_t hash;
lmo_entry_t *e;
lmo_archive_t *ar;
if (!key || !_lmo_active_catalog)
return -2;
uint32_t hash = sfh_hash(key, keylen);
const lmo_entry_t *e = lmo_find_entry(cat, hash);
if (!e)
return false;
hash = lmo_canon_hash(key, keylen);
for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
{
if ((e = lmo_find_entry(ar, hash)) != NULL)
{
*out = ar->mmap + ntohl(e->offset);
*out = cat->data + ntohl(e->offset);
*outlen = ntohl(e->length);
return 0;
}
}
return -1;
if (*out + *outlen > cat->end)
return false;
return true;
}
......@@ -2,6 +2,7 @@
* lmo - Lua Machine Objects - General header
*
* Copyright (C) 2009-2012 Jo-Philipp Wich <jow@openwrt.org>
* Copyright (C) 2018 Matthias Schiffer <mschiffer@universe-factory.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -19,27 +20,9 @@
#ifndef _TEMPLATE_LMO_H_
#define _TEMPLATE_LMO_H_
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fnmatch.h>
#include <dirent.h>
#include <ctype.h>
#include <limits.h>
#if (defined(__GNUC__) && defined(__i386__))
#define sfh_get16(d) (*((const uint16_t *) (d)))
#else
#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+(uint32_t)(((const uint8_t *)(d))[0]) )
#endif
struct lmo_entry {
......@@ -48,34 +31,23 @@ struct lmo_entry {
uint32_t offset;
uint32_t length;
} __attribute__((packed));
typedef struct lmo_entry lmo_entry_t;
struct lmo_archive {
int fd;
int length;
uint32_t size;
lmo_entry_t *index;
char *mmap;
char *end;
struct lmo_archive *next;
};
typedef struct lmo_archive lmo_archive_t;
struct lmo_catalog {
char lang[6];
struct lmo_archive *archives;
struct lmo_catalog *next;
size_t length;
const lmo_entry_t *index;
char *data;
const char *end;
};
typedef struct lmo_catalog lmo_catalog_t;
int lmo_load_catalog(const char *lang, const char *dir);
int lmo_change_catalog(const char *lang);
int lmo_translate(const char *key, int keylen, char **out, int *outlen);
uint32_t sfh_hash(const void *input, size_t len);
bool lmo_load(lmo_catalog_t *cat, const char *file);
void lmo_unload(lmo_catalog_t *cat);
bool lmo_translate(const lmo_catalog_t *cat, const char *key, size_t keylen, const char **out, size_t *outlen);
#endif
/*
* LuCI Template - Lua binding
* gluon-web Template - Lua binding
*
* Copyright (C) 2009 Jo-Philipp Wich <jow@openwrt.org>
* Copyright (C) 2018 Matthias Schiffer <mschiffer@universe-factory.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -16,7 +17,20 @@
* limitations under the License.
*/
#include "template_lualib.h"
#include "template_parser.h"
#include "template_utils.h"
#include "template_lmo.h"
#include <lualib.h>
#include <lauxlib.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define TEMPLATE_CATALOG "gluon.web.template.parser.catalog"
static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname)
{
......@@ -61,61 +75,76 @@ static int template_L_parse_string(lua_State *L)
static int template_L_pcdata(lua_State *L)
{
size_t len = 0;
const char *str = luaL_checklstring(L, 1, &len);
char *res = pcdata(str, len);
size_t inlen, outlen;
char *out;
const char *in = luaL_checklstring(L, 1, &inlen);
if (!pcdata(in, inlen, &out, &outlen))
return 0;
if (res != NULL)
{
lua_pushstring(L, res);
free(res);
lua_pushlstring(L, out, outlen);
free(out);
return 1;
}
static int template_L_load_catalog(lua_State *L)
{
const char *file = luaL_checkstring(L, 1);
lmo_catalog_t *cat = lua_newuserdata(L, sizeof(*cat));
if (!lmo_load(cat, file)) {
lua_pop(L, 1);
return 0;
}
static int template_L_load_catalog(lua_State *L) {
const char *lang = luaL_optstring(L, 1, "en");
const char *dir = luaL_optstring(L, 2, NULL);
lua_pushboolean(L, !lmo_load_catalog(lang, dir));
luaL_getmetatable(L, TEMPLATE_CATALOG);
lua_setmetatable(L, -2);
return 1;
}
static int template_L_translate(lua_State *L) {
size_t len;
char *tr;
int trlen;
const char *key = luaL_checklstring(L, 1, &len);
switch (lmo_translate(key, len, &tr, &trlen))
static int template_catalog_call(lua_State *L)
{
case 0:
lua_pushlstring(L, tr, trlen);
return 1;
case -1:
size_t inlen, outlen;
lmo_catalog_t *cat = luaL_checkudata(L, 1, TEMPLATE_CATALOG);
const char *in = luaL_checklstring(L, 2, &inlen), *out;
if (!lmo_translate(cat, in, inlen, &out, &outlen))
return 0;
}
lua_pushnil(L);
lua_pushstring(L, "no catalog loaded");
return 2;
lua_pushlstring(L, out, outlen);
return 1;
}
static int template_catalog_gc(lua_State *L)
{
lmo_catalog_t *cat = luaL_checkudata(L, 1, TEMPLATE_CATALOG);
lmo_unload(cat);
return 0;
}
/* module table */
static const luaL_reg R[] = {
{ "parse", template_L_parse },
{ "parse_string", template_L_parse_string },
{ "pcdata", template_L_pcdata },
{ "load_catalog", template_L_load_catalog },
{ "translate", template_L_translate },
{}
};
static const luaL_reg template_catalog_methods[] = {
{ "__call", template_catalog_call },
{ "__gc", template_catalog_gc },
{}
};
__attribute__ ((visibility("default")))
LUALIB_API int luaopen_gluon_web_template_parser(lua_State *L) {
luaL_register(L, TEMPLATE_LUALIB_META, R);
luaL_register(L, "gluon.web.template.parser", R);
luaL_newmetatable(L, TEMPLATE_CATALOG);
luaL_register(L, NULL, template_catalog_methods);
lua_pop(L, 1);
return 1;
}