gluon-web: add package
The gluon-web package is basically a stripped-down and refactored version of the LuCI base.
This commit is contained in:
parent
a06541623e
commit
e4b74be506
128
contrib/i18n-scan.pl
Executable file
128
contrib/i18n-scan.pl
Executable file
@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Text::Balanced qw(extract_bracketed extract_delimited extract_tagged);
|
||||||
|
|
||||||
|
@ARGV >= 1 || die "Usage: $0 <source direcory>\n";
|
||||||
|
|
||||||
|
|
||||||
|
my %stringtable;
|
||||||
|
|
||||||
|
sub dec_lua_str
|
||||||
|
{
|
||||||
|
my $s = shift;
|
||||||
|
$s =~ s/[\s\n]+/ /g;
|
||||||
|
$s =~ s/\\n/\n/g;
|
||||||
|
$s =~ s/\\t/\t/g;
|
||||||
|
$s =~ s/\\(.)/$1/g;
|
||||||
|
$s =~ s/^ //;
|
||||||
|
$s =~ s/ $//;
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dec_tpl_str
|
||||||
|
{
|
||||||
|
my $s = shift;
|
||||||
|
$s =~ s/-$//;
|
||||||
|
$s =~ s/[\s\n]+/ /g;
|
||||||
|
$s =~ s/^ //;
|
||||||
|
$s =~ s/ $//;
|
||||||
|
$s =~ s/\\/\\\\/g;
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( open F, "find @ARGV -type f '(' -name '*.html' -o -name '*.lua' ')' |" )
|
||||||
|
{
|
||||||
|
while( defined( my $file = readline F ) )
|
||||||
|
{
|
||||||
|
chomp $file;
|
||||||
|
|
||||||
|
if( open S, "< $file" )
|
||||||
|
{
|
||||||
|
local $/ = undef;
|
||||||
|
my $raw = <S>;
|
||||||
|
close S;
|
||||||
|
|
||||||
|
|
||||||
|
my $text = $raw;
|
||||||
|
|
||||||
|
while( $text =~ s/ ^ .*? (?:translate|translatef|i18n|_) [\n\s]* \( /(/sgx )
|
||||||
|
{
|
||||||
|
( my $code, $text ) = extract_bracketed($text, q{('")});
|
||||||
|
|
||||||
|
$code =~ s/\\\n/ /g;
|
||||||
|
$code =~ s/^\([\n\s]*//;
|
||||||
|
$code =~ s/[\n\s]*\)$//;
|
||||||
|
|
||||||
|
my $res = "";
|
||||||
|
my $sub = "";
|
||||||
|
|
||||||
|
if( $code =~ /^['"]/ )
|
||||||
|
{
|
||||||
|
while( defined $sub )
|
||||||
|
{
|
||||||
|
( $sub, $code ) = extract_delimited($code, q{'"}, q{\s*(?:\.\.\s*)?});
|
||||||
|
|
||||||
|
if( defined $sub && length($sub) > 2 )
|
||||||
|
{
|
||||||
|
$res .= substr $sub, 1, length($sub) - 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
undef $sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif( $code =~ /^(\[=*\[)/ )
|
||||||
|
{
|
||||||
|
my $stag = quotemeta $1;
|
||||||
|
my $etag = $stag;
|
||||||
|
$etag =~ s/\[/]/g;
|
||||||
|
|
||||||
|
( $res ) = extract_tagged($code, $stag, $etag);
|
||||||
|
|
||||||
|
$res =~ s/^$stag//;
|
||||||
|
$res =~ s/$etag$//;
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = dec_lua_str($res);
|
||||||
|
$stringtable{$res}++ if $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$text = $raw;
|
||||||
|
|
||||||
|
while( $text =~ s/ ^ .*? <% -? [:_] /<%/sgx )
|
||||||
|
{
|
||||||
|
( my $code, $text ) = extract_tagged($text, '<%', '%>');
|
||||||
|
|
||||||
|
if( defined $code )
|
||||||
|
{
|
||||||
|
$code = dec_tpl_str(substr $code, 2, length($code) - 4);
|
||||||
|
$stringtable{$code}++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close F;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( open C, "| msgcat -" )
|
||||||
|
{
|
||||||
|
printf C "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\n";
|
||||||
|
|
||||||
|
foreach my $key ( sort keys %stringtable )
|
||||||
|
{
|
||||||
|
if( length $key )
|
||||||
|
{
|
||||||
|
$key =~ s/"/\\"/g;
|
||||||
|
printf C "msgid \"%s\"\nmsgstr \"\"\n\n", $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close C;
|
||||||
|
}
|
53
package/gluon-web/Makefile
Normal file
53
package/gluon-web/Makefile
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=gluon-web
|
||||||
|
PKG_VERSION:=1
|
||||||
|
|
||||||
|
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|
||||||
|
include ../gluon.mk
|
||||||
|
|
||||||
|
PKG_CONFIG_DEPENDS += $(GLUON_I18N_CONFIG)
|
||||||
|
|
||||||
|
PKG_INSTALL:=1
|
||||||
|
|
||||||
|
|
||||||
|
define Package/gluon-web
|
||||||
|
SECTION:=gluon
|
||||||
|
CATEGORY:=Gluon
|
||||||
|
TITLE:=Minimal Lua web framework derived from LuCI
|
||||||
|
DEPENDS:=+luci-lib-jsonc +luci-lib-nixio
|
||||||
|
endef
|
||||||
|
|
||||||
|
define lang-config
|
||||||
|
|
||||||
|
config GLUON_WEB_LANG_$(1)
|
||||||
|
bool "$(GLUON_LANG_$(1)) language support for gluon-web"
|
||||||
|
depends on PACKAGE_gluon-web
|
||||||
|
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/gluon-web/config
|
||||||
|
$(foreach lang,$(GLUON_SUPPORTED_LANGS),$(call lang-config,$(lang)))
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Prepare
|
||||||
|
mkdir -p $(PKG_BUILD_DIR)
|
||||||
|
$(CP) ./src/* $(PKG_BUILD_DIR)/
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
$(call Build/Compile/Default)
|
||||||
|
$(call GluonBuildI18N,gluon-web,i18n)
|
||||||
|
$(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/gluon-web/install
|
||||||
|
$(CP) ./files/* $(1)/
|
||||||
|
$(CP) $(PKG_INSTALL_DIR)/* $(1)/
|
||||||
|
$(CP) $(PKG_BUILD_DIR)/luadest/* $(1)/
|
||||||
|
$(call GluonInstallI18N,gluon-web,$(1))
|
||||||
|
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,gluon-web))
|
14
package/gluon-web/files/lib/gluon/web/view/csrftoken.html
Normal file
14
package/gluon-web/files/lib/gluon/web/view/csrftoken.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<%#
|
||||||
|
Copyright 2015 Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
Licensed to the public under the Apache License 2.0.
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<h2 name="content"><%:Form token mismatch%></h2>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<p class="alert-message"><%:The submitted security token is invalid or already expired!%></p>
|
||||||
|
|
||||||
|
<p><%:
|
||||||
|
In order to prevent unauthorized access to the system, your request has
|
||||||
|
been blocked.
|
||||||
|
%></p>
|
9
package/gluon-web/files/lib/gluon/web/view/error404.html
Normal file
9
package/gluon-web/files/lib/gluon/web/view/error404.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<%#
|
||||||
|
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
Licensed to the public under the Apache License 2.0.
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<h2 name="content">404 <%:Not Found%></h2>
|
||||||
|
<p><%:Sorry, the object you requested was not found.%></p>
|
||||||
|
<tt><%=pcdata(message)%></tt>
|
9
package/gluon-web/files/lib/gluon/web/view/error500.html
Normal file
9
package/gluon-web/files/lib/gluon/web/view/error500.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<%#
|
||||||
|
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
Licensed to the public under the Apache License 2.0.
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<h2 name="content">500 <%:Internal Server Error%></h2>
|
||||||
|
<p><%:Sorry, the server encountered an unexpected error.%></p>
|
||||||
|
<pre class="error500"><%=pcdata(message)%></pre>
|
3
package/gluon-web/files/lib/gluon/web/view/layout.html
Normal file
3
package/gluon-web/files/lib/gluon/web/view/layout.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<%
|
||||||
|
include("themes/" .. theme .. "/layout")
|
||||||
|
%>
|
@ -0,0 +1,20 @@
|
|||||||
|
<div<%=
|
||||||
|
attr("data-prefix", id) ..
|
||||||
|
attr("data-dynlist", {
|
||||||
|
type = self.datatype,
|
||||||
|
optional = self.datatype and self.optional,
|
||||||
|
}) ..
|
||||||
|
attr("data-size", self.size) ..
|
||||||
|
attr("data-placeholder", self.placeholder)
|
||||||
|
%>>
|
||||||
|
<%
|
||||||
|
for i, val in ipairs(self:cfgvalue()) do
|
||||||
|
%>
|
||||||
|
<input class="gluon-input-text" value="<%=pcdata(val)%>" data-update="change" type="text"<%=
|
||||||
|
attr("id", id .. "." .. i) ..
|
||||||
|
attr("name", id) ..
|
||||||
|
attr("size", self.size) ..
|
||||||
|
attr("placeholder", self.placeholder)
|
||||||
|
%> /><br />
|
||||||
|
<% end %>
|
||||||
|
</div>
|
28
package/gluon-web/files/lib/gluon/web/view/model/form.html
Normal file
28
package/gluon-web/files/lib/gluon/web/view/model/form.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<form method="post" enctype="multipart/form-data" action="<%=url(request)%>">
|
||||||
|
<input type="hidden" name="token" value="<%=token%>" />
|
||||||
|
<input type="hidden" name="<%=id%>" value="1" />
|
||||||
|
|
||||||
|
<div class="gluon-map" id="gluon-<%=self.config%>">
|
||||||
|
<% if self.title and #self.title > 0 then %><h2 name="content"><%=self.title%></h2><% end %>
|
||||||
|
<% if self.description and #self.description > 0 then %><div class="gluon-map-descr"><%=self.description%></div><% end %>
|
||||||
|
<% self:render_children(renderer) %>
|
||||||
|
</div>
|
||||||
|
<%- if self.message then %>
|
||||||
|
<div><%=self.message%></div>
|
||||||
|
<%- end %>
|
||||||
|
<%- if self.errmessage then %>
|
||||||
|
<div class="error"><%=self.errmessage%></div>
|
||||||
|
<%- end %>
|
||||||
|
<div class="gluon-page-actions">
|
||||||
|
<%- if self.submit ~= false then %>
|
||||||
|
<input class="gluon-button gluon-button-submit" type="submit" value="
|
||||||
|
<%- if not self.submit then -%><%-:Save-%><%-else-%><%=pcdata(self.submit)%><%end-%>
|
||||||
|
" />
|
||||||
|
<% end %>
|
||||||
|
<%- if self.reset ~= false then %>
|
||||||
|
<input class="gluon-button gluon-button-reset" type="reset" value="
|
||||||
|
<%- if not self.reset then -%><%-:Reset-%><%-else-%><%=pcdata(self.reset)%><%end-%>
|
||||||
|
" />
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,5 @@
|
|||||||
|
<input class="gluon-input-checkbox" data-update="click change" type="checkbox" value="1"<%=
|
||||||
|
attr("id", id) .. attr("name", id) ..
|
||||||
|
attr("checked", self:cfgvalue() and "checked")
|
||||||
|
%> />
|
||||||
|
<label<%= attr("for", id)%>></label>
|
41
package/gluon-web/files/lib/gluon/web/view/model/lvalue.html
Normal file
41
package/gluon-web/files/lib/gluon/web/view/model/lvalue.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<%
|
||||||
|
local i, key
|
||||||
|
local br = self.orientation == "horizontal" and ' ' or '<br />'
|
||||||
|
%>
|
||||||
|
|
||||||
|
<% if self.widget == "select" then %>
|
||||||
|
<select class="gluon-input-select" data-update="change"<%=
|
||||||
|
attr("id", id) ..
|
||||||
|
attr("name", id) ..
|
||||||
|
attr("size", self.size)
|
||||||
|
%>>
|
||||||
|
<% for i, key in pairs(self.keylist) do -%>
|
||||||
|
<option<%=
|
||||||
|
attr("id", id.."."..key) ..
|
||||||
|
attr("value", key) ..
|
||||||
|
attr("data-index", i) ..
|
||||||
|
attr("data-depends", self:deplist(self.valdeps[i])) ..
|
||||||
|
attr("selected", (self:cfgvalue() == key) and "selected")
|
||||||
|
%>><%=pcdata(self.vallist[i])%></option>
|
||||||
|
<%- end %>
|
||||||
|
</select>
|
||||||
|
<% elseif self.widget == "radio" then %>
|
||||||
|
<div>
|
||||||
|
<% for i, key in pairs(self.keylist) do %>
|
||||||
|
<label<%=
|
||||||
|
attr("data-index", i) ..
|
||||||
|
attr("data-depends", self:deplist(self.valdeps[i]))
|
||||||
|
%>>
|
||||||
|
<input class="gluon-input-radio" data-update="click change" type="radio"<%=
|
||||||
|
attr("id", id.."."..key) ..
|
||||||
|
attr("name", id) ..
|
||||||
|
attr("value", key) ..
|
||||||
|
attr("checked", (self:cfgvalue() == key) and "checked")
|
||||||
|
%> />
|
||||||
|
<label<%= attr("for", id.."-"..key)%>></label>
|
||||||
|
<%=pcdata(self.vallist[i])%>
|
||||||
|
</label>
|
||||||
|
<% if i == self.size then write(br) end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
@ -0,0 +1,28 @@
|
|||||||
|
<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>
|
@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
12
package/gluon-web/files/lib/gluon/web/view/model/value.html
Normal file
12
package/gluon-web/files/lib/gluon/web/view/model/value.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<input data-update="change"<%=
|
||||||
|
attr("id", id) ..
|
||||||
|
attr("name", id) ..
|
||||||
|
attr("type", self.password and "password" or "text") ..
|
||||||
|
attr("class", self.password and "gluon-input-password" or "gluon-input-text") ..
|
||||||
|
attr("value", self:cfgvalue()) ..
|
||||||
|
attr("size", self.size) ..
|
||||||
|
attr("placeholder", self.placeholder) ..
|
||||||
|
attr("maxlength", self.maxlength) ..
|
||||||
|
attr("data-type", self.datatype) ..
|
||||||
|
attr("data-optional", self.datatype and self.optional)
|
||||||
|
%> />
|
@ -0,0 +1,18 @@
|
|||||||
|
<div class="gluon-value<% if self.error then %> gluon-value-error<% end %>" id="value-<%=id%>" data-index="<%=self.index%>"<%= attr("data-depends", self:deplist()) %>>
|
||||||
|
<%- if self.title and #self.title > 0 then -%>
|
||||||
|
<label class="gluon-value-title"<%= attr("for", id) %>>
|
||||||
|
<%-=self.title-%>
|
||||||
|
</label>
|
||||||
|
<div class="gluon-value-field">
|
||||||
|
<%- end -%>
|
||||||
|
<% if self.subtemplate then include(self.subtemplate) end %>
|
||||||
|
<% if self.description and #self.description > 0 then -%>
|
||||||
|
<br />
|
||||||
|
<div class="gluon-value-description">
|
||||||
|
<%=self.description%>
|
||||||
|
</div>
|
||||||
|
<%- end %>
|
||||||
|
<%- if self.title and #self.title > 0 then -%>
|
||||||
|
</div>
|
||||||
|
<%- end -%>
|
||||||
|
</div>
|
@ -0,0 +1,6 @@
|
|||||||
|
<%
|
||||||
|
for _, map in ipairs(maps) do
|
||||||
|
map:render(renderer)
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
<script type="text/javascript" src="<%=resource%>/gluon-web.js"></script>
|
8
package/gluon-web/files/lib/gluon/web/www/index.html
Normal file
8
package/gluon-web/files/lib/gluon/web/www/index.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<!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>
|
File diff suppressed because one or more lines are too long
56
package/gluon-web/i18n/de.po
Normal file
56
package/gluon-web/i18n/de.po
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"PO-Revision-Date: 2013-03-29 12:13+0200\n"
|
||||||
|
"Last-Translator: Matthias Schiffer <mschiffer@universe-factory.net>\n"
|
||||||
|
"Language-Team: German\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"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."
|
||||||
|
|
||||||
|
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 ""
|
54
package/gluon-web/i18n/fr.po
Normal file
54
package/gluon-web/i18n/fr.po
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"PO-Revision-Date: 2013-12-22 17:11+0200\n"
|
||||||
|
"Last-Translator: goofy <pierre.gaufillet@gmail.com>\n"
|
||||||
|
"Language-Team: French\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"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 ""
|
45
package/gluon-web/i18n/gluon-web.pot
Normal file
45
package/gluon-web/i18n/gluon-web.pot
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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 ""
|
531
package/gluon-web/javascript/gluon-web.js
Normal file
531
package/gluon-web/javascript/gluon-web.js
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
Copyright 2017 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.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Build using:
|
||||||
|
|
||||||
|
uglifyjs javascript/gluon-web.js -o files/lib/gluon/web/www/static/resources/gluon-web.js -c -m --support-ie8
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var dep_entries = {};
|
||||||
|
|
||||||
|
function Int(x) {
|
||||||
|
return (/^-?\d+$/.test(x) ? +x : NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dec(x) {
|
||||||
|
return (/^-?\d*\.?\d+?$/.test(x) ? +x : NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
var validators = {
|
||||||
|
|
||||||
|
'integer': function() {
|
||||||
|
return !isNaN(Int(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
'uinteger': function() {
|
||||||
|
return (Int(this) >= 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
'float': function() {
|
||||||
|
return !isNaN(Dec(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
'ufloat': function() {
|
||||||
|
return (Dec(this) >= 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
'ipaddr': function() {
|
||||||
|
return validators.ip4addr.apply(this) ||
|
||||||
|
validators.ip6addr.apply(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
'ip4addr': function() {
|
||||||
|
if (this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||||
|
return (RegExp.$1 >= 0) && (RegExp.$1 <= 255) &&
|
||||||
|
(RegExp.$2 >= 0) && (RegExp.$2 <= 255) &&
|
||||||
|
(RegExp.$3 >= 0) && (RegExp.$3 <= 255) &&
|
||||||
|
(RegExp.$4 >= 0) && (RegExp.$4 <= 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
'ip6addr': function() {
|
||||||
|
if (this.indexOf('::') < 0)
|
||||||
|
return (this.match(/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i) != null);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(this.indexOf(':::') >= 0) || this.match(/::.+::/) ||
|
||||||
|
this.match(/^:[^:]/) || this.match(/[^:]:$/)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (this.match(/^(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}$/i))
|
||||||
|
return true;
|
||||||
|
if (this.match(/^(?:[a-f0-9]{1,4}:){7}:$/i))
|
||||||
|
return true;
|
||||||
|
if (this.match(/^:(?::[a-f0-9]{1,4}){7}$/i))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
'wpakey': function() {
|
||||||
|
var v = this;
|
||||||
|
|
||||||
|
if (v.length == 64)
|
||||||
|
return (v.match(/^[a-f0-9]{64}$/i) != null);
|
||||||
|
else
|
||||||
|
return (v.length >= 8) && (v.length <= 63);
|
||||||
|
},
|
||||||
|
|
||||||
|
'range': function(min, max) {
|
||||||
|
var val = Dec(this);
|
||||||
|
return (val >= +min && val <= +max);
|
||||||
|
},
|
||||||
|
|
||||||
|
'min': function(min) {
|
||||||
|
return (Dec(this) >= +min);
|
||||||
|
},
|
||||||
|
|
||||||
|
'max': function(max) {
|
||||||
|
return (Dec(this) <= +max);
|
||||||
|
},
|
||||||
|
|
||||||
|
'irange': function(min, max) {
|
||||||
|
var val = Int(this);
|
||||||
|
return (val >= +min && val <= +max);
|
||||||
|
},
|
||||||
|
|
||||||
|
'imin': function(min) {
|
||||||
|
return (Int(this) >= +min);
|
||||||
|
},
|
||||||
|
|
||||||
|
'imax': function(max) {
|
||||||
|
return (Int(this) <= +max);
|
||||||
|
},
|
||||||
|
|
||||||
|
'minlength': function(min) {
|
||||||
|
return ((''+this).length >= +min);
|
||||||
|
},
|
||||||
|
|
||||||
|
'maxlength': function(max) {
|
||||||
|
return ((''+this).length <= +max);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function compile(type) {
|
||||||
|
var v;
|
||||||
|
if (type.match(/^([^\(]+)\(([^,]+),([^\)]+)\)$/) && (v = validators[RegExp.$1]) !== undefined) {
|
||||||
|
return function() {
|
||||||
|
return v(RegExp.$2, RegExp.$3);
|
||||||
|
}
|
||||||
|
} else if (type.match(/^([^\(]+)\(([^,\)]+)\)$/) && (v = validators[RegExp.$1]) !== undefined) {
|
||||||
|
return function() {
|
||||||
|
return v(RegExp.$2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return validators[type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkvalue(target, ref) {
|
||||||
|
var t = document.getElementById(target);
|
||||||
|
var value;
|
||||||
|
|
||||||
|
if (t) {
|
||||||
|
if (t.type == "checkbox") {
|
||||||
|
value = t.checked;
|
||||||
|
} else if (t.value) {
|
||||||
|
value = t.value;
|
||||||
|
} else {
|
||||||
|
value = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (value == ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(deps) {
|
||||||
|
for (var i=0; i < deps.length; i++) {
|
||||||
|
var stat = true;
|
||||||
|
|
||||||
|
for (var j in deps[i]) {
|
||||||
|
stat = (stat && checkvalue(j, deps[i][j]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var state = false;
|
||||||
|
for (var id in dep_entries) {
|
||||||
|
var entry = dep_entries[id];
|
||||||
|
var node = document.getElementById(id);
|
||||||
|
var parent = document.getElementById(entry.parent);
|
||||||
|
|
||||||
|
if (node && node.parentNode && !check(entry.deps)) {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
|
state = true;
|
||||||
|
} else if (parent && (!node || !node.parentNode) && check(entry.deps)) {
|
||||||
|
var next = undefined;
|
||||||
|
|
||||||
|
for (next = parent.firstChild; next; next = next.nextSibling) {
|
||||||
|
if (next.getAttribute && parseInt(next.getAttribute('data-index'), 10) > entry.index) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!next) {
|
||||||
|
parent.appendChild(entry.node);
|
||||||
|
} else {
|
||||||
|
parent.insertBefore(entry.node, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide optionals widget if no choices remaining
|
||||||
|
if (parent && parent.parentNode && parent.getAttribute('data-optionals'))
|
||||||
|
parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(obj, type, callback, mode) {
|
||||||
|
if (!obj.addEventListener) {
|
||||||
|
obj.attachEvent('on' + type,
|
||||||
|
function() {
|
||||||
|
var e = window.event;
|
||||||
|
|
||||||
|
if (!e.target && e.srcElement)
|
||||||
|
e.target = e.srcElement;
|
||||||
|
|
||||||
|
return !!callback(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
obj.addEventListener(type, callback, !!mode);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_dynlist(parent, datatype, optional) {
|
||||||
|
var prefix = parent.getAttribute('data-prefix');
|
||||||
|
var holder = parent.getAttribute('data-placeholder');
|
||||||
|
|
||||||
|
|
||||||
|
function dynlist_redraw(focus, add, del) {
|
||||||
|
var values = [];
|
||||||
|
|
||||||
|
while (parent.firstChild) {
|
||||||
|
var n = parent.firstChild;
|
||||||
|
var i = +n.index;
|
||||||
|
|
||||||
|
if (i != del) {
|
||||||
|
if (n.nodeName.toLowerCase() == 'input')
|
||||||
|
values.push(n.value || '');
|
||||||
|
else if (n.nodeName.toLowerCase() == 'select')
|
||||||
|
values[values.length-1] = n.options[n.selectedIndex].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.removeChild(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add >= 0) {
|
||||||
|
focus = add + 1;
|
||||||
|
values.splice(add, 0, '');
|
||||||
|
} else if (!optional && values.length == 0) {
|
||||||
|
values.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i <= values.length; i++) {
|
||||||
|
var t = document.createElement('input');
|
||||||
|
t.id = prefix + '.' + i;
|
||||||
|
t.name = prefix;
|
||||||
|
t.value = values[i-1];
|
||||||
|
t.type = 'text';
|
||||||
|
t.index = i;
|
||||||
|
t.className = 'gluon-input-text';
|
||||||
|
|
||||||
|
if (holder)
|
||||||
|
t.placeholder = holder;
|
||||||
|
|
||||||
|
parent.appendChild(t);
|
||||||
|
|
||||||
|
if (datatype)
|
||||||
|
validate_field(t, false, datatype);
|
||||||
|
|
||||||
|
bind(t, 'keydown', dynlist_keydown);
|
||||||
|
bind(t, 'keypress', dynlist_keypress);
|
||||||
|
|
||||||
|
if (i == focus) {
|
||||||
|
t.focus();
|
||||||
|
} else if (-i == focus) {
|
||||||
|
t.focus();
|
||||||
|
|
||||||
|
/* force cursor to end */
|
||||||
|
var v = t.value;
|
||||||
|
t.value = ' '
|
||||||
|
t.value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optional || values.length > 1) {
|
||||||
|
var b = document.createElement('span');
|
||||||
|
b.className = 'gluon-remove';
|
||||||
|
|
||||||
|
parent.appendChild(b);
|
||||||
|
|
||||||
|
bind(b, 'click', dynlist_btnclick(false));
|
||||||
|
|
||||||
|
parent.appendChild(document.createElement('br'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = document.createElement('span');
|
||||||
|
b.className = 'gluon-add';
|
||||||
|
|
||||||
|
parent.appendChild(b);
|
||||||
|
|
||||||
|
bind(b, 'click', dynlist_btnclick(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
function dynlist_keypress(ev) {
|
||||||
|
ev = ev ? ev : window.event;
|
||||||
|
|
||||||
|
var se = ev.target ? ev.target : ev.srcElement;
|
||||||
|
|
||||||
|
if (se.nodeType == 3)
|
||||||
|
se = se.parentNode;
|
||||||
|
|
||||||
|
switch (ev.keyCode) {
|
||||||
|
/* backspace, delete */
|
||||||
|
case 8:
|
||||||
|
case 46:
|
||||||
|
if (se.value.length == 0) {
|
||||||
|
if (ev.preventDefault)
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* enter, arrow up, arrow down */
|
||||||
|
case 13:
|
||||||
|
case 38:
|
||||||
|
case 40:
|
||||||
|
if (ev.preventDefault)
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dynlist_keydown(ev) {
|
||||||
|
ev = ev ? ev : window.event;
|
||||||
|
|
||||||
|
var se = ev.target ? ev.target : ev.srcElement;
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
var prev, next;
|
||||||
|
|
||||||
|
if (se) {
|
||||||
|
if (se.nodeType == 3)
|
||||||
|
se = se.parentNode;
|
||||||
|
|
||||||
|
index = se.index;
|
||||||
|
|
||||||
|
prev = se.previousSibling;
|
||||||
|
while (prev && prev.name != prefix)
|
||||||
|
prev = prev.previousSibling;
|
||||||
|
|
||||||
|
next = se.nextSibling;
|
||||||
|
while (next && next.name != prefix)
|
||||||
|
next = next.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ev.keyCode) {
|
||||||
|
/* backspace, delete */
|
||||||
|
case 8:
|
||||||
|
case 46:
|
||||||
|
var del = (se.nodeName.toLowerCase() == 'select')
|
||||||
|
? true : (se.value.length == 0);
|
||||||
|
|
||||||
|
if (del) {
|
||||||
|
if (ev.preventDefault)
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
var focus = se.index;
|
||||||
|
if (ev.keyCode == 8)
|
||||||
|
focus = -focus+1;
|
||||||
|
|
||||||
|
dynlist_redraw(focus, -1, index);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* enter */
|
||||||
|
case 13:
|
||||||
|
dynlist_redraw(-1, index, -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* arrow up */
|
||||||
|
case 38:
|
||||||
|
if (prev)
|
||||||
|
prev.focus();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* arrow down */
|
||||||
|
case 40:
|
||||||
|
if (next)
|
||||||
|
next.focus();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dynlist_btnclick(add) {
|
||||||
|
return function(ev) {
|
||||||
|
ev = ev ? ev : window.event;
|
||||||
|
|
||||||
|
var se = ev.target ? ev.target : ev.srcElement;
|
||||||
|
var input = se.previousSibling;
|
||||||
|
while (input && input.name != prefix) {
|
||||||
|
input = input.previousSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add) {
|
||||||
|
dynlist_keydown({
|
||||||
|
target: input,
|
||||||
|
keyCode: 13
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
input.value = '';
|
||||||
|
|
||||||
|
dynlist_keydown({
|
||||||
|
target: input,
|
||||||
|
keyCode: 8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynlist_redraw(NaN, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_field(field, optional, type) {
|
||||||
|
var check = compile(type);
|
||||||
|
if (!check)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var validator = function() {
|
||||||
|
if (!field.form)
|
||||||
|
return;
|
||||||
|
|
||||||
|
field.className = field.className.replace(/ gluon-input-invalid/g, '');
|
||||||
|
|
||||||
|
var value = (field.options && field.options.selectedIndex > -1)
|
||||||
|
? field.options[field.options.selectedIndex].value : field.value;
|
||||||
|
|
||||||
|
if (!(((value.length == 0) && optional) || check.apply(value)))
|
||||||
|
field.className += ' gluon-input-invalid';
|
||||||
|
};
|
||||||
|
|
||||||
|
bind(field, "blur", validator);
|
||||||
|
bind(field, "keyup", validator);
|
||||||
|
|
||||||
|
if (field.nodeName == 'SELECT') {
|
||||||
|
bind(field, "change", validator);
|
||||||
|
bind(field, "click", validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
validator();
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(obj, dep, index) {
|
||||||
|
var entry = dep_entries[obj.id];
|
||||||
|
if (!entry) {
|
||||||
|
entry = {
|
||||||
|
"node": obj,
|
||||||
|
"parent": obj.parentNode.id,
|
||||||
|
"deps": [],
|
||||||
|
"index": index
|
||||||
|
};
|
||||||
|
dep_entries[obj.id] = entry;
|
||||||
|
}
|
||||||
|
entry.deps.push(dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var nodes;
|
||||||
|
|
||||||
|
nodes = document.querySelectorAll('[data-depends]');
|
||||||
|
|
||||||
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
|
var index = parseInt(node.getAttribute('data-index'), 10);
|
||||||
|
var depends = JSON.parse(node.getAttribute('data-depends'));
|
||||||
|
if (!isNaN(index) && depends.length > 0) {
|
||||||
|
for (var alt = 0; alt < depends.length; alt++) {
|
||||||
|
add(node, depends[alt], index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = document.querySelectorAll('[data-update]');
|
||||||
|
|
||||||
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
|
var events = node.getAttribute('data-update').split(' ');
|
||||||
|
for (var j = 0, event; (event = events[j]) !== undefined; j++) {
|
||||||
|
bind(node, event, update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = document.querySelectorAll('[data-type]');
|
||||||
|
|
||||||
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
|
validate_field(node, node.getAttribute('data-optional') === 'true',
|
||||||
|
node.getAttribute('data-type'));
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = document.querySelectorAll('[data-dynlist]');
|
||||||
|
|
||||||
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
|
var list = JSON.parse(node.getAttribute('data-dynlist'));
|
||||||
|
|
||||||
|
init_dynlist(node, list.type, list.optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
})();
|
||||||
|
})();
|
3
package/gluon-web/luasrc/lib/gluon/web/www/cgi-bin/gluon
Executable file
3
package/gluon-web/luasrc/lib/gluon/web/www/cgi-bin/gluon
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/lua
|
||||||
|
require "gluon.web.cgi"
|
||||||
|
gluon.web.cgi.run()
|
38
package/gluon-web/luasrc/usr/lib/lua/gluon/web/cgi.lua
Normal file
38
package/gluon-web/luasrc/usr/lib/lua/gluon/web/cgi.lua
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
-- 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")
|
||||||
|
|
||||||
|
-- Limited source to avoid endless blocking
|
||||||
|
local function limitsource(handle, limit)
|
||||||
|
limit = limit or 0
|
||||||
|
local BLOCKSIZE = 2048
|
||||||
|
|
||||||
|
return function()
|
||||||
|
if limit < 1 then
|
||||||
|
handle:close()
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit
|
||||||
|
limit = limit - read
|
||||||
|
|
||||||
|
local chunk = handle:read(read)
|
||||||
|
if not chunk then handle:close() end
|
||||||
|
return chunk
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function run()
|
||||||
|
local http = gluon.web.http.Http(
|
||||||
|
nixio.getenv(),
|
||||||
|
limitsource(io.stdin, tonumber(nixio.getenv("CONTENT_LENGTH"))),
|
||||||
|
io.stdout
|
||||||
|
)
|
||||||
|
|
||||||
|
gluon.web.dispatcher.httpdispatch(http)
|
||||||
|
end
|
258
package/gluon-web/luasrc/usr/lib/lua/gluon/web/dispatcher.lua
Normal file
258
package/gluon-web/luasrc/usr/lib/lua/gluon/web/dispatcher.lua
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
-- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
-- Licensed to the public under the Apache License 2.0.
|
||||||
|
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
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)
|
||||||
|
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 = {}
|
||||||
|
local weights = {}
|
||||||
|
local star = 0
|
||||||
|
|
||||||
|
local function add(lang, q)
|
||||||
|
if not weights[lang] then
|
||||||
|
table.insert(langs, lang)
|
||||||
|
weights[lang] = q
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for match in accept:gmatch("[^,]+") do
|
||||||
|
local lang = match:match('^%s*([^%s;-_]+)')
|
||||||
|
local q = tonumber(match:match(';q=(%S+)%s*$') or 1)
|
||||||
|
|
||||||
|
if lang == '*' then
|
||||||
|
star = q
|
||||||
|
elseif lang and q > 0 then
|
||||||
|
add(lang, q)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
add('en', star)
|
||||||
|
|
||||||
|
table.sort(langs, function(a, b)
|
||||||
|
return (weights[a] or 0) > (weights[b] or 0)
|
||||||
|
end)
|
||||||
|
|
||||||
|
for _, lang in ipairs(langs) do
|
||||||
|
if renderer.setlanguage(lang) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function dispatch(http, request)
|
||||||
|
local tree = {nodes={}}
|
||||||
|
local nodes = {[''] = tree}
|
||||||
|
|
||||||
|
local function _node(path, create)
|
||||||
|
local name = table.concat(path, ".")
|
||||||
|
local c = nodes[name]
|
||||||
|
|
||||||
|
if not c and create then
|
||||||
|
local last = table.remove(path)
|
||||||
|
local parent = _node(path, true)
|
||||||
|
|
||||||
|
c = {nodes={}}
|
||||||
|
parent.nodes[last] = c
|
||||||
|
nodes[name] = c
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Init template engine
|
||||||
|
local function attr(key, val)
|
||||||
|
if not val then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(val) == "table" then
|
||||||
|
val = util.serialize_json(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.format(' %s="%s"', key, util.pcdata(tostring(val)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local renderer = tpl.renderer(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,
|
||||||
|
url = function(path) return build_url(http, path) end,
|
||||||
|
}, { __index = _G }))
|
||||||
|
|
||||||
|
local subdisp = setmetatable({
|
||||||
|
node = function(...)
|
||||||
|
return _node({...})
|
||||||
|
end,
|
||||||
|
|
||||||
|
entry = function(path, target, title, order)
|
||||||
|
local c = _node(path, true)
|
||||||
|
|
||||||
|
c.target = target
|
||||||
|
c.title = title
|
||||||
|
c.order = order
|
||||||
|
|
||||||
|
return c
|
||||||
|
end,
|
||||||
|
|
||||||
|
alias = function(...)
|
||||||
|
local req = {...}
|
||||||
|
return function()
|
||||||
|
http:redirect(build_url(http, req))
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
call = function(func, ...)
|
||||||
|
local args = {...}
|
||||||
|
return function()
|
||||||
|
func(http, renderer, unpack(args))
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
template = function(view)
|
||||||
|
return function()
|
||||||
|
renderer.render("layout", {content = view})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
model = function(name)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
_ = function(text)
|
||||||
|
return text
|
||||||
|
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
|
||||||
|
load_ctl(path)
|
||||||
|
end
|
||||||
|
for path in (fs.glob(base .. "*/*.lua") or function() end) do
|
||||||
|
load_ctl(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set_language(renderer, http:getenv("HTTP_ACCEPT_LANGUAGE") or "")
|
||||||
|
|
||||||
|
createtree()
|
||||||
|
|
||||||
|
|
||||||
|
local node = _node(request)
|
||||||
|
|
||||||
|
if not node or not node.target then
|
||||||
|
http:status(404, "Not Found")
|
||||||
|
renderer.render("layout", { content = "error404", message =
|
||||||
|
"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
|
||||||
|
"If this URL belongs to an extension, make sure it is properly installed.\n"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
http:parse_input(node.filehandler)
|
||||||
|
|
||||||
|
local ok, err = pcall(node.target)
|
||||||
|
if not ok then
|
||||||
|
http:status(500, "Internal Server Error")
|
||||||
|
renderer.render("layout", { content = "error500", message =
|
||||||
|
"Failed to execute dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
|
||||||
|
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)")
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
123
package/gluon-web/luasrc/usr/lib/lua/gluon/web/http.lua
Normal file
123
package/gluon-web/luasrc/usr/lib/lua/gluon/web/http.lua
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
-- 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"
|
||||||
|
|
||||||
|
|
||||||
|
Http = util.class()
|
||||||
|
function Http:__init__(env, input, output)
|
||||||
|
self.input = input
|
||||||
|
self.output = output
|
||||||
|
|
||||||
|
self.request = {
|
||||||
|
env = env,
|
||||||
|
headers = {},
|
||||||
|
params = protocol.urldecode_params(env.QUERY_STRING or ""),
|
||||||
|
}
|
||||||
|
self.headers = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function push_headers(self)
|
||||||
|
if self.eoh then return end
|
||||||
|
|
||||||
|
for _, header in pairs(self.headers) do
|
||||||
|
self.output:write(string.format("%s: %s\r\n", header[1], header[2]))
|
||||||
|
end
|
||||||
|
self.output:write("\r\n")
|
||||||
|
|
||||||
|
self.eoh = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:parse_input(filehandler)
|
||||||
|
protocol.parse_message_body(
|
||||||
|
self.input,
|
||||||
|
self.request,
|
||||||
|
filehandler
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:formvalue(name)
|
||||||
|
return self:formvaluetable(name)[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:formvaluetable(name)
|
||||||
|
return self.request.params[name] or {}
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:getenv(name)
|
||||||
|
return self.request.env[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:close()
|
||||||
|
if not self.output then return end
|
||||||
|
|
||||||
|
push_headers(self)
|
||||||
|
|
||||||
|
self.output:flush()
|
||||||
|
self.output:close()
|
||||||
|
self.output = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:header(key, value)
|
||||||
|
self.headers[key:lower()] = {key, value}
|
||||||
|
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
|
||||||
|
|
||||||
|
function Http:status(code, request)
|
||||||
|
if not self.output or self.code then return end
|
||||||
|
|
||||||
|
code = code or 200
|
||||||
|
request = request or "OK"
|
||||||
|
self.code = code
|
||||||
|
self.output:write(string.format("Status: %i %s\r\n", code, request))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:write(content)
|
||||||
|
if not self.output then return end
|
||||||
|
|
||||||
|
self:status()
|
||||||
|
|
||||||
|
self:prepare_content("text/html; charset=utf-8")
|
||||||
|
|
||||||
|
if not self.headers["cache-control"] then
|
||||||
|
self:header("Cache-Control", "no-cache")
|
||||||
|
self:header("Expires", "0")
|
||||||
|
end
|
||||||
|
|
||||||
|
push_headers(self)
|
||||||
|
self.output:write(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http:redirect(url)
|
||||||
|
self:status(302, "Found")
|
||||||
|
self:header("Location", url)
|
||||||
|
self:close()
|
||||||
|
end
|
268
package/gluon-web/luasrc/usr/lib/lua/gluon/web/http/protocol.lua
Normal file
268
package/gluon-web/luasrc/usr/lib/lua/gluon/web/http/protocol.lua
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
-- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
|
||||||
|
local function pump(src, snk)
|
||||||
|
while true do
|
||||||
|
local chunk, src_err = src()
|
||||||
|
local ret, snk_err = snk(chunk, src_err)
|
||||||
|
|
||||||
|
if not (chunk and ret) then
|
||||||
|
local err = src_err or snk_err
|
||||||
|
if err then
|
||||||
|
return nil, err
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function urlencode(s)
|
||||||
|
return (string.gsub(s, '[^a-zA-Z0-9%-_%.~]',
|
||||||
|
function(c)
|
||||||
|
local ret = ''
|
||||||
|
|
||||||
|
for i = 1, string.len(c) do
|
||||||
|
ret = ret .. string.format('%%%02X', string.byte(c, i, i))
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- the "+" sign to " " - and return the decoded string.
|
||||||
|
function urldecode(str, no_plus)
|
||||||
|
|
||||||
|
local function chrdec(hex)
|
||||||
|
return string.char(tonumber(hex, 16))
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(str) == "string" then
|
||||||
|
if not no_plus then
|
||||||
|
str = str:gsub("+", " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
str = str:gsub("%%(%x%x)", chrdec)
|
||||||
|
end
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
local function initval(tbl, key)
|
||||||
|
if not tbl[key] then
|
||||||
|
tbl[key] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(tbl[key], "")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function appendval(tbl, key, chunk)
|
||||||
|
local t = tbl[key]
|
||||||
|
t[#t] = t[#t] .. chunk
|
||||||
|
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)
|
||||||
|
local params = {}
|
||||||
|
|
||||||
|
if url:find("?") then
|
||||||
|
url = url:gsub("^.+%?([^?]+)", "%1")
|
||||||
|
end
|
||||||
|
|
||||||
|
for pair in url:gmatch("[^&;]+") do
|
||||||
|
|
||||||
|
-- find key and value
|
||||||
|
local key = urldecode(pair:match("^([^=]+)"))
|
||||||
|
local val = urldecode(pair:match("^[^=]+=(.+)$"))
|
||||||
|
|
||||||
|
-- store
|
||||||
|
if key and key:len() > 0 then
|
||||||
|
initval(params, key)
|
||||||
|
if val then
|
||||||
|
appendval(params, key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return params
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Content-Type. Stores all extracted data associated with its parameter name
|
||||||
|
-- in the params table withing the given message object. Multiple parameter
|
||||||
|
-- values are stored as tables, ordinary ones as strings.
|
||||||
|
-- If an optional file callback function is given then it is feeded with the
|
||||||
|
-- file contents chunk by chunk and only the extracted file name is stored
|
||||||
|
-- within the params table. The callback function will be called subsequently
|
||||||
|
-- with three arguments:
|
||||||
|
-- o Table containing decoded (name, file) and raw (headers) mime header data
|
||||||
|
-- o String value containing a chunk of the file data
|
||||||
|
-- o Boolean which indicates wheather the current chunk is the last one (eof)
|
||||||
|
function mimedecode_message_body(src, msg, filecb)
|
||||||
|
|
||||||
|
if msg and msg.env.CONTENT_TYPE then
|
||||||
|
msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not msg.mime_boundary then
|
||||||
|
return nil, "Invalid Content-Type found"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local tlen = 0
|
||||||
|
local inhdr = false
|
||||||
|
local field = nil
|
||||||
|
local store = nil
|
||||||
|
local lchunk = nil
|
||||||
|
|
||||||
|
local function parse_headers(chunk, field)
|
||||||
|
local stat
|
||||||
|
repeat
|
||||||
|
chunk, stat = chunk:gsub(
|
||||||
|
"^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n",
|
||||||
|
function(k,v)
|
||||||
|
field.headers[k] = v
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
)
|
||||||
|
until stat == 0
|
||||||
|
|
||||||
|
chunk, stat = chunk:gsub("^\r\n","")
|
||||||
|
|
||||||
|
-- End of headers
|
||||||
|
if stat > 0 then
|
||||||
|
if field.headers["Content-Disposition"] then
|
||||||
|
if field.headers["Content-Disposition"]:match("^form%-data; ") then
|
||||||
|
field.name = field.headers["Content-Disposition"]:match('name="(.-)"')
|
||||||
|
field.file = field.headers["Content-Disposition"]:match('filename="(.+)"$')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not field.headers["Content-Type"] then
|
||||||
|
field.headers["Content-Type"] = "text/plain"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if field.name then
|
||||||
|
initval(msg.params, field.name)
|
||||||
|
if field.file then
|
||||||
|
appendval(msg.params, field.name, field.file)
|
||||||
|
store = filecb
|
||||||
|
else
|
||||||
|
store = function(hdr, buf, eof)
|
||||||
|
appendval(msg.params, field.name, buf)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
store = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return chunk, true
|
||||||
|
end
|
||||||
|
|
||||||
|
return chunk, false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function snk(chunk)
|
||||||
|
|
||||||
|
tlen = tlen + (chunk and #chunk or 0)
|
||||||
|
|
||||||
|
if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
|
||||||
|
return nil, "Message body size exceeds Content-Length"
|
||||||
|
end
|
||||||
|
|
||||||
|
if chunk and not lchunk then
|
||||||
|
lchunk = "\r\n" .. chunk
|
||||||
|
|
||||||
|
elseif lchunk then
|
||||||
|
local data = lchunk .. (chunk or "")
|
||||||
|
local spos, epos, found
|
||||||
|
|
||||||
|
repeat
|
||||||
|
spos, epos = data:find("\r\n--" .. msg.mime_boundary .. "\r\n", 1, true)
|
||||||
|
|
||||||
|
if not spos then
|
||||||
|
spos, epos = data:find("\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if spos then
|
||||||
|
local predata = data:sub(1, spos - 1)
|
||||||
|
|
||||||
|
if inhdr then
|
||||||
|
predata, eof = parse_headers(predata, field)
|
||||||
|
|
||||||
|
if not eof then
|
||||||
|
return nil, "Invalid MIME section header"
|
||||||
|
elseif not field.name then
|
||||||
|
return nil, "Invalid Content-Disposition header"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if store then
|
||||||
|
store(field, predata, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
field = { headers = { } }
|
||||||
|
found = true
|
||||||
|
|
||||||
|
data, eof = parse_headers(data:sub(epos + 1, #data), field)
|
||||||
|
inhdr = not eof
|
||||||
|
end
|
||||||
|
until not spos
|
||||||
|
|
||||||
|
if found then
|
||||||
|
-- We found at least some boundary. Save
|
||||||
|
-- the unparsed remaining data for the
|
||||||
|
-- next chunk.
|
||||||
|
lchunk, data = data, nil
|
||||||
|
else
|
||||||
|
-- There was a complete chunk without a boundary. Parse it as headers or
|
||||||
|
-- append it as data, depending on our current state.
|
||||||
|
if inhdr then
|
||||||
|
lchunk, eof = parse_headers(data, field)
|
||||||
|
inhdr = not eof
|
||||||
|
else
|
||||||
|
-- We're inside data, so append the data. Note that we only append
|
||||||
|
-- lchunk, not all of data, since there is a chance that chunk
|
||||||
|
-- contains half a boundary. Assuming that each chunk is at least the
|
||||||
|
-- boundary in size, this should prevent problems
|
||||||
|
if store then
|
||||||
|
store(field, lchunk, false)
|
||||||
|
end
|
||||||
|
lchunk, chunk = chunk, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return pump(src, snk)
|
||||||
|
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
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if msg.env.CONTENT_TYPE:match("^multipart/form%-data") then
|
||||||
|
return mimedecode_message_body(src, msg, filecb)
|
||||||
|
end
|
||||||
|
end
|
466
package/gluon-web/luasrc/usr/lib/lua/gluon/web/model.lua
Normal file
466
package/gluon-web/luasrc/usr/lib/lua/gluon/web/model.lua
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
-- Licensed to the public under the Apache License 2.0.
|
||||||
|
|
||||||
|
module("gluon.web.model", package.seeall)
|
||||||
|
|
||||||
|
local util = require("gluon.web.util")
|
||||||
|
|
||||||
|
local fs = require("nixio.fs")
|
||||||
|
local datatypes = require("gluon.web.model.datatypes")
|
||||||
|
local dispatcher = require("gluon.web.dispatcher")
|
||||||
|
local class = util.class
|
||||||
|
local instanceof = util.instanceof
|
||||||
|
|
||||||
|
FORM_NODATA = 0
|
||||||
|
FORM_VALID = 1
|
||||||
|
FORM_INVALID = -1
|
||||||
|
|
||||||
|
-- Loads a model from given file, creating an environment and returns it
|
||||||
|
function load(name, renderer)
|
||||||
|
local modeldir = util.libpath() .. "/model/"
|
||||||
|
|
||||||
|
if not fs.access(modeldir..name..".lua") then
|
||||||
|
error("Model '" .. name .. "' not found!")
|
||||||
|
end
|
||||||
|
|
||||||
|
local func = assert(loadfile(modeldir..name..".lua"))
|
||||||
|
|
||||||
|
local env = {
|
||||||
|
translate=renderer.translate,
|
||||||
|
translatef=renderer.translatef,
|
||||||
|
}
|
||||||
|
|
||||||
|
setfenv(func, setmetatable(env, {__index =
|
||||||
|
function(tbl, key)
|
||||||
|
return _M[key] or _G[key]
|
||||||
|
end
|
||||||
|
}))
|
||||||
|
|
||||||
|
local models = { func() }
|
||||||
|
|
||||||
|
for k, model in ipairs(models) do
|
||||||
|
if not instanceof(model, Node) then
|
||||||
|
error("model definition returned an invalid model object")
|
||||||
|
end
|
||||||
|
model.index = k
|
||||||
|
end
|
||||||
|
|
||||||
|
return models
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_datatype(code)
|
||||||
|
local match, arg, arg2
|
||||||
|
|
||||||
|
match, arg, arg2 = code:match('^([^%(]+)%(([^,]+),([^%)]+)%)$')
|
||||||
|
if match then
|
||||||
|
return datatypes[match], {arg, arg2}
|
||||||
|
end
|
||||||
|
|
||||||
|
match, arg = code:match('^([^%(]+)%(([^%)]+)%)$')
|
||||||
|
if match then
|
||||||
|
return datatypes[match], {arg}
|
||||||
|
end
|
||||||
|
|
||||||
|
return datatypes[code], {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function verify_datatype(dt, value)
|
||||||
|
if dt then
|
||||||
|
local c, args = parse_datatype(dt)
|
||||||
|
assert(c, "Invalid datatype")
|
||||||
|
return c(value, unpack(args))
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Node = class()
|
||||||
|
|
||||||
|
function Node:__init__(title, description, name)
|
||||||
|
self.children = {}
|
||||||
|
self.title = title or ""
|
||||||
|
self.description = description or ""
|
||||||
|
self.name = name
|
||||||
|
self.index = nil
|
||||||
|
self.parent = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:append(obj)
|
||||||
|
table.insert(self.children, obj)
|
||||||
|
obj.index = #self.children
|
||||||
|
obj.parent = self
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:id_suffix()
|
||||||
|
return self.name or (self.index and tostring(self.index)) or '_'
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:id()
|
||||||
|
local prefix = self.parent and self.parent:id() or "id"
|
||||||
|
|
||||||
|
return prefix.."."..self:id_suffix()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:parse(http)
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
child:parse(http)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:render(renderer, scope)
|
||||||
|
if self.template then
|
||||||
|
local env = setmetatable({
|
||||||
|
self = self,
|
||||||
|
id = self:id(),
|
||||||
|
scope = scope,
|
||||||
|
}, {__index = scope})
|
||||||
|
renderer.render(self.template, env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:render_children(renderer, scope)
|
||||||
|
for _, node in ipairs(self.children) do
|
||||||
|
node:render(renderer, scope)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:resolve_depends()
|
||||||
|
local updated = false
|
||||||
|
for _, node in ipairs(self.children) do
|
||||||
|
update = updated or node:resolve_depends()
|
||||||
|
end
|
||||||
|
return updated
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:handle()
|
||||||
|
for _, node in ipairs(self.children) do
|
||||||
|
node:handle()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Template = class(Node)
|
||||||
|
|
||||||
|
function Template:__init__(template)
|
||||||
|
Node.__init__(self)
|
||||||
|
self.template = template
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Form = class(Node)
|
||||||
|
|
||||||
|
function Form:__init__(...)
|
||||||
|
Node.__init__(self, ...)
|
||||||
|
self.template = "model/form"
|
||||||
|
end
|
||||||
|
|
||||||
|
function Form:submitstate(http)
|
||||||
|
return http:getenv("REQUEST_METHOD") == "POST" and http:formvalue(self:id()) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Form:parse(http)
|
||||||
|
if not self:submitstate(http) then
|
||||||
|
self.state = FORM_NODATA
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Node.parse(self, http)
|
||||||
|
|
||||||
|
while self:resolve_depends() do end
|
||||||
|
|
||||||
|
for _, s in ipairs(self.children) do
|
||||||
|
for _, v in ipairs(s.children) do
|
||||||
|
if v.state == FORM_INVALID then
|
||||||
|
self.state = FORM_INVALID
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.state = FORM_VALID
|
||||||
|
end
|
||||||
|
|
||||||
|
function Form:handle()
|
||||||
|
if self.state == FORM_VALID then
|
||||||
|
Node.handle(self)
|
||||||
|
self:write()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Form:write()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Form:section(t, ...)
|
||||||
|
assert(instanceof(t, Section), "class must be a descendent of Section")
|
||||||
|
|
||||||
|
local obj = t(...)
|
||||||
|
self:append(obj)
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Section = class(Node)
|
||||||
|
|
||||||
|
function Section:__init__(...)
|
||||||
|
Node.__init__(self, ...)
|
||||||
|
self.fields = {}
|
||||||
|
self.template = "model/section"
|
||||||
|
end
|
||||||
|
|
||||||
|
function Section:option(t, option, title, description, ...)
|
||||||
|
assert(instanceof(t, AbstractValue), "class must be a descendant of AbstractValue")
|
||||||
|
|
||||||
|
local obj = t(title, description, option, ...)
|
||||||
|
self:append(obj)
|
||||||
|
self.fields[option] = obj
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
AbstractValue = class(Node)
|
||||||
|
|
||||||
|
function AbstractValue:__init__(option, ...)
|
||||||
|
Node.__init__(self, option, ...)
|
||||||
|
self.deps = {}
|
||||||
|
|
||||||
|
self.default = nil
|
||||||
|
self.size = nil
|
||||||
|
self.optional = false
|
||||||
|
|
||||||
|
self.template = "model/valuewrapper"
|
||||||
|
|
||||||
|
self.state = FORM_NODATA
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:depends(field, value)
|
||||||
|
local deps
|
||||||
|
if instanceof(field, Node) then
|
||||||
|
deps = { [field] = value }
|
||||||
|
else
|
||||||
|
deps = field
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(self.deps, deps)
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:deplist(section, deplist)
|
||||||
|
local deps = {}
|
||||||
|
|
||||||
|
for _, d in ipairs(deplist or self.deps) do
|
||||||
|
local a = {}
|
||||||
|
for k, v in pairs(d) do
|
||||||
|
a[k:id()] = v
|
||||||
|
end
|
||||||
|
table.insert(deps, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
if next(deps) then
|
||||||
|
return deps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:defaultvalue()
|
||||||
|
return self.default
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:formvalue(http)
|
||||||
|
return http:formvalue(self:id())
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:cfgvalue()
|
||||||
|
if self.state == FORM_NODATA then
|
||||||
|
return self:defaultvalue()
|
||||||
|
else
|
||||||
|
return self.data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:add_error(type, msg)
|
||||||
|
self.error = msg or type
|
||||||
|
|
||||||
|
if type == "invalid" then
|
||||||
|
self.tag_invalid = true
|
||||||
|
elseif type == "missing" then
|
||||||
|
self.tag_missing = true
|
||||||
|
end
|
||||||
|
|
||||||
|
self.state = FORM_INVALID
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:reset()
|
||||||
|
self.error = nil
|
||||||
|
self.tag_invalid = nil
|
||||||
|
self.tag_missing = nil
|
||||||
|
self.data = nil
|
||||||
|
self.state = FORM_NODATA
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:parse(http)
|
||||||
|
self.data = self:formvalue(http)
|
||||||
|
|
||||||
|
local ok, err = self:validate()
|
||||||
|
if not ok then
|
||||||
|
if type(self.data) ~= "string" or #self.data > 0 then
|
||||||
|
self:add_error("invalid", err)
|
||||||
|
else
|
||||||
|
self:add_error("missing", err)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.state = FORM_VALID
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:resolve_depends()
|
||||||
|
if self.state == FORM_NODATA or #self.deps == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, d in ipairs(self.deps) do
|
||||||
|
local valid = true
|
||||||
|
for k, v in pairs(d) do
|
||||||
|
if k.state ~= FORM_VALID or k.data ~= v then
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if valid then return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:reset()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:validate()
|
||||||
|
if self.data and verify_datatype(self.datatype, self.data) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(self.data) == "string" and #self.data == 0 then
|
||||||
|
self.data = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.data == nil then
|
||||||
|
return self.optional
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:handle()
|
||||||
|
if self.state == FORM_VALID then
|
||||||
|
self:write(self.data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function AbstractValue:write(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Value = class(AbstractValue)
|
||||||
|
|
||||||
|
function Value:__init__(...)
|
||||||
|
AbstractValue.__init__(self, ...)
|
||||||
|
self.subtemplate = "model/value"
|
||||||
|
self.keylist = {}
|
||||||
|
self.vallist = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Flag = class(AbstractValue)
|
||||||
|
|
||||||
|
function Flag:__init__(...)
|
||||||
|
AbstractValue.__init__(self, ...)
|
||||||
|
self.subtemplate = "model/fvalue"
|
||||||
|
|
||||||
|
self.default = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Flag:formvalue(http)
|
||||||
|
return http:formvalue(self:id()) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Flag:validate()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
ListValue = class(AbstractValue)
|
||||||
|
|
||||||
|
function ListValue:__init__(...)
|
||||||
|
AbstractValue.__init__(self, ...)
|
||||||
|
self.subtemplate = "model/lvalue"
|
||||||
|
|
||||||
|
self.size = 1
|
||||||
|
self.widget = "select"
|
||||||
|
|
||||||
|
self.keylist = {}
|
||||||
|
self.vallist = {}
|
||||||
|
self.valdeps = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function ListValue:value(key, val, ...)
|
||||||
|
if util.contains(self.keylist, key) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
val = val or key
|
||||||
|
table.insert(self.keylist, tostring(key))
|
||||||
|
table.insert(self.vallist, tostring(val))
|
||||||
|
table.insert(self.valdeps, {...})
|
||||||
|
end
|
||||||
|
|
||||||
|
function ListValue:validate()
|
||||||
|
return util.contains(self.keylist, self.data)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
DynamicList = class(AbstractValue)
|
||||||
|
|
||||||
|
function DynamicList:__init__(...)
|
||||||
|
AbstractValue.__init__(self, ...)
|
||||||
|
self.subtemplate = "model/dynlist"
|
||||||
|
end
|
||||||
|
|
||||||
|
function DynamicList:defaultvalue()
|
||||||
|
local value = self.default
|
||||||
|
|
||||||
|
if type(value) == "table" then
|
||||||
|
return value
|
||||||
|
else
|
||||||
|
return { value }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DynamicList:formvalue(http)
|
||||||
|
return http:formvaluetable(self:id())
|
||||||
|
end
|
||||||
|
|
||||||
|
function DynamicList:validate()
|
||||||
|
if self.data == nil then
|
||||||
|
self.data = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if #self.data == 0 then
|
||||||
|
return self.optional
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in ipairs(self.data) do
|
||||||
|
if not verify_datatype(self.datatype, v) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
TextValue = class(AbstractValue)
|
||||||
|
|
||||||
|
function TextValue:__init__(...)
|
||||||
|
AbstractValue.__init__(self, ...)
|
||||||
|
self.subtemplate = "model/tvalue"
|
||||||
|
end
|
@ -0,0 +1,167 @@
|
|||||||
|
-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
|
||||||
|
-- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
-- Licensed to the public under the Apache License 2.0.
|
||||||
|
|
||||||
|
local tonumber = tonumber
|
||||||
|
|
||||||
|
|
||||||
|
module "gluon.web.model.datatypes"
|
||||||
|
|
||||||
|
|
||||||
|
function bool(val)
|
||||||
|
if val == "1" or val == "yes" or val == "on" or val == "true" then
|
||||||
|
return true
|
||||||
|
elseif val == "0" or val == "no" or val == "off" or val == "false" then
|
||||||
|
return true
|
||||||
|
elseif val == "" or val == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dec(val)
|
||||||
|
if val:match('^%-?%d*%.?%d+$') then
|
||||||
|
return tonumber(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function int(val)
|
||||||
|
if val:match('^%-?%d+$') then
|
||||||
|
return tonumber(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function uinteger(val)
|
||||||
|
local n = int(val)
|
||||||
|
return (n ~= nil and n >= 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function integer(val)
|
||||||
|
return (int(val) ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ufloat(val)
|
||||||
|
local n = dec(val)
|
||||||
|
return (n ~= nil and n >= 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function float(val)
|
||||||
|
return (dec(val) ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ipaddr(val)
|
||||||
|
return ip4addr(val) or ip6addr(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ip4addr(val)
|
||||||
|
local g = '(%d%d?%d?)'
|
||||||
|
local v1, v2, v3, v4 = val:match('^'..((g..'%.'):rep(3))..g..'$')
|
||||||
|
local n1, n2, n3, n4 = tonumber(v1), tonumber(v2), tonumber(v3), tonumber(v4)
|
||||||
|
|
||||||
|
if not (n1 and n2 and n3 and n4) then return false end
|
||||||
|
|
||||||
|
return (
|
||||||
|
(n1 >= 0) and (n1 <= 255) and
|
||||||
|
(n2 >= 0) and (n2 <= 255) and
|
||||||
|
(n3 >= 0) and (n3 <= 255) and
|
||||||
|
(n4 >= 0) and (n4 <= 255)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ip6addr(val)
|
||||||
|
local g1 = '%x%x?%x?%x?'
|
||||||
|
|
||||||
|
if not val:match('::') then
|
||||||
|
return val:match('^'..((g1..':'):rep(7))..g1..'$') ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
val:match(':::') or val:match('::.+::') or
|
||||||
|
val:match('^:[^:]') or val:match('[^:]:$')
|
||||||
|
then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local g0 = '%x?%x?%x?%x?'
|
||||||
|
for i = 2, 7 do
|
||||||
|
if val:match('^'..((g0..':'):rep(i))..g0..'$') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if val:match('^'..((g1..':'):rep(7))..':$') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if val:match('^:'..((':'..g1):rep(7))..'$') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function wpakey(val)
|
||||||
|
if #val == 64 then
|
||||||
|
return (val:match("^%x+$") ~= nil)
|
||||||
|
else
|
||||||
|
return (#val >= 8) and (#val <= 63)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function range(val, vmin, vmax)
|
||||||
|
return min(val, vmin) and max(val, vmax)
|
||||||
|
end
|
||||||
|
|
||||||
|
function min(val, min)
|
||||||
|
val = dec(val)
|
||||||
|
min = tonumber(min)
|
||||||
|
|
||||||
|
if val ~= nil and min ~= nil then
|
||||||
|
return (val >= min)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function max(val, max)
|
||||||
|
val = dec(val)
|
||||||
|
max = tonumber(max)
|
||||||
|
|
||||||
|
if val ~= nil and max ~= nil then
|
||||||
|
return (val <= max)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function irange(val, vmin, vmax)
|
||||||
|
return integer(val) and range(val, vmin, vmax)
|
||||||
|
end
|
||||||
|
|
||||||
|
function imin(val, vmin)
|
||||||
|
return integer(val) and min(val, vmin)
|
||||||
|
end
|
||||||
|
|
||||||
|
function imax(val, vmax)
|
||||||
|
return integer(val) and max(val, vmax)
|
||||||
|
end
|
||||||
|
|
||||||
|
function minlength(val, min)
|
||||||
|
min = tonumber(min)
|
||||||
|
|
||||||
|
if min ~= nil then
|
||||||
|
return (#val >= min)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function maxlength(val, max)
|
||||||
|
max = tonumber(max)
|
||||||
|
|
||||||
|
if max ~= nil then
|
||||||
|
return (#val <= max)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
92
package/gluon-web/luasrc/usr/lib/lua/gluon/web/template.lua
Normal file
92
package/gluon-web/luasrc/usr/lib/lua/gluon/web/template.lua
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
-- Copyright 2017 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 tostring, setmetatable, setfenv, pcall, assert = tostring, setmetatable, setfenv, pcall, assert
|
||||||
|
|
||||||
|
|
||||||
|
module "gluon.web.template"
|
||||||
|
|
||||||
|
local viewdir = util.libpath() .. "/view/"
|
||||||
|
local i18ndir = util.libpath() .. "/i18n/"
|
||||||
|
|
||||||
|
function renderer(env)
|
||||||
|
local ctx = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function render_template(name, template, scope)
|
||||||
|
scope = scope or {}
|
||||||
|
|
||||||
|
local locals = {
|
||||||
|
renderer = ctx,
|
||||||
|
translate = ctx.translate,
|
||||||
|
translatef = ctx.translatef,
|
||||||
|
include = function(name)
|
||||||
|
ctx.render(name, scope)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
setfenv(template, setmetatable({}, {
|
||||||
|
__index = function(tbl, key)
|
||||||
|
return scope[key] or env[key] or locals[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)"))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Render a certain template.
|
||||||
|
-- @param name Template name
|
||||||
|
-- @param scope Scope to assign to template (optional)
|
||||||
|
function ctx.render(name, scope)
|
||||||
|
local sourcefile = viewdir .. name .. ".html"
|
||||||
|
local template, _, err = tparser.parse(sourcefile)
|
||||||
|
|
||||||
|
assert(template, "Failed to load template '" .. name .. "'.\n" ..
|
||||||
|
"Error while parsing template '" .. sourcefile .. "':\n" ..
|
||||||
|
(err or "Unknown syntax error"))
|
||||||
|
|
||||||
|
render_template(name, template, scope)
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
return tparser.load_catalog(lang, i18ndir)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ctx.translate(key)
|
||||||
|
return tparser.translate(key) or key
|
||||||
|
end
|
||||||
|
|
||||||
|
function ctx.translatef(key, ...)
|
||||||
|
local t = ctx.translate(key)
|
||||||
|
return t and t:format(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
end
|
100
package/gluon-web/luasrc/usr/lib/lua/gluon/web/util.lua
Normal file
100
package/gluon-web/luasrc/usr/lib/lua/gluon/web/util.lua
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||||
|
-- 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"
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 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)
|
||||||
|
while object do
|
||||||
|
if object == class then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local mt = getmetatable(object)
|
||||||
|
object = mt and mt.__index
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- String and data manipulation routines
|
||||||
|
--
|
||||||
|
|
||||||
|
function 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
|
16
package/gluon-web/src/Makefile
Normal file
16
package/gluon-web/src/Makefile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
all: compile
|
||||||
|
|
||||||
|
%.o: %.c
|
||||||
|
$(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -c -o $@ $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f parser.so *.o
|
||||||
|
|
||||||
|
parser.so: template_parser.o template_utils.o template_lmo.o template_lualib.o
|
||||||
|
$(CC) $(LDFLAGS) -shared -o $@ $^
|
||||||
|
|
||||||
|
compile: parser.so
|
||||||
|
|
||||||
|
install: compile
|
||||||
|
mkdir -p $(DESTDIR)/usr/lib/lua/gluon/web/template
|
||||||
|
cp parser.so $(DESTDIR)/usr/lib/lua/gluon/web/template/parser.so
|
288
package/gluon-web/src/template_lmo.c
Normal file
288
package/gluon-web/src/template_lmo.c
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* lmo - Lua Machine Objects - Base functions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009-2010 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"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 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;
|
||||||
|
hash = (hash << 16) ^ tmp;
|
||||||
|
data += 2*sizeof(uint16_t);
|
||||||
|
hash += hash >> 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle end cases */
|
||||||
|
switch (rem) {
|
||||||
|
case 3: hash += sfh_get16(data);
|
||||||
|
hash ^= hash << 16;
|
||||||
|
hash ^= data[sizeof(uint16_t)] << 18;
|
||||||
|
hash += hash >> 11;
|
||||||
|
break;
|
||||||
|
case 2: hash += sfh_get16(data);
|
||||||
|
hash ^= hash << 11;
|
||||||
|
hash += hash >> 17;
|
||||||
|
break;
|
||||||
|
case 1: hash += *data;
|
||||||
|
hash ^= hash << 10;
|
||||||
|
hash += hash >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force "avalanching" of final 127 bits */
|
||||||
|
hash ^= hash << 3;
|
||||||
|
hash += hash >> 5;
|
||||||
|
hash ^= hash << 4;
|
||||||
|
hash += hash >> 17;
|
||||||
|
hash ^= hash << 25;
|
||||||
|
hash += hash >> 6;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
int in = -1;
|
||||||
|
uint32_t idx_offset = 0;
|
||||||
|
struct stat s;
|
||||||
|
|
||||||
|
lmo_archive_t *ar = NULL;
|
||||||
|
|
||||||
|
if (stat(file, &s) == -1)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if ((in = open(file, O_RDONLY)) == -1)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
|
||||||
|
{
|
||||||
|
memset(ar, 0, sizeof(*ar));
|
||||||
|
|
||||||
|
ar->fd = in;
|
||||||
|
ar->size = s.st_size;
|
||||||
|
|
||||||
|
fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
|
||||||
|
|
||||||
|
if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
idx_offset = ntohl(*((const uint32_t *)
|
||||||
|
(ar->mmap + ar->size - sizeof(uint32_t))));
|
||||||
|
|
||||||
|
if (idx_offset >= ar->size)
|
||||||
|
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;
|
||||||
|
|
||||||
|
return ar;
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (in > -1)
|
||||||
|
close(in);
|
||||||
|
|
||||||
|
if (ar != NULL)
|
||||||
|
{
|
||||||
|
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
|
||||||
|
munmap(ar->mmap, ar->size);
|
||||||
|
|
||||||
|
free(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
ar->next = cat->archives;
|
||||||
|
cat->archives = ar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!m)
|
||||||
|
break;
|
||||||
|
|
||||||
|
r = m - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
l = m + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lmo_translate(const char *key, int keylen, char **out, int *outlen)
|
||||||
|
{
|
||||||
|
uint32_t hash;
|
||||||
|
lmo_entry_t *e;
|
||||||
|
lmo_archive_t *ar;
|
||||||
|
|
||||||
|
if (!key || !_lmo_active_catalog)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
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);
|
||||||
|
*outlen = ntohl(e->length);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
81
package/gluon-web/src/template_lmo.h
Normal file
81
package/gluon-web/src/template_lmo.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* lmo - Lua Machine Objects - General header
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TEMPLATE_LMO_H_
|
||||||
|
#define _TEMPLATE_LMO_H_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.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 {
|
||||||
|
uint32_t key_id;
|
||||||
|
uint32_t val_id;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#endif
|
121
package/gluon-web/src/template_lualib.c
Normal file
121
package/gluon-web/src/template_lualib.c
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* LuCI Template - Lua binding
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 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_lualib.h"
|
||||||
|
|
||||||
|
static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname)
|
||||||
|
{
|
||||||
|
int lua_status, rv;
|
||||||
|
|
||||||
|
if (!parser)
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushinteger(L, errno);
|
||||||
|
lua_pushstring(L, strerror(errno));
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_status = lua_load(L, template_reader, parser, chunkname);
|
||||||
|
|
||||||
|
if (lua_status == 0)
|
||||||
|
rv = 1;
|
||||||
|
else
|
||||||
|
rv = template_error(L, parser);
|
||||||
|
|
||||||
|
template_close(parser);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int template_L_parse(lua_State *L)
|
||||||
|
{
|
||||||
|
const char *file = luaL_checkstring(L, 1);
|
||||||
|
struct template_parser *parser = template_open(file);
|
||||||
|
|
||||||
|
return template_L_do_parse(L, parser, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int template_L_parse_string(lua_State *L)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
const char *str = luaL_checklstring(L, 1, &len);
|
||||||
|
struct template_parser *parser = template_string(str, len);
|
||||||
|
|
||||||
|
return template_L_do_parse(L, parser, "[string]");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (res != NULL)
|
||||||
|
{
|
||||||
|
lua_pushstring(L, res);
|
||||||
|
free(res);
|
||||||
|
|
||||||
|
return 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));
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
lua_pushlstring(L, tr, trlen);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case -1:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushstring(L, "no catalog loaded");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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 },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
LUALIB_API int luaopen_gluon_web_template_parser(lua_State *L) {
|
||||||
|
luaL_register(L, TEMPLATE_LUALIB_META, R);
|
||||||
|
return 1;
|
||||||
|
}
|
30
package/gluon-web/src/template_lualib.h
Normal file
30
package/gluon-web/src/template_lualib.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* LuCI Template - Lua library header
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TEMPLATE_LUALIB_H_
|
||||||
|
#define _TEMPLATE_LUALIB_H_
|
||||||
|
|
||||||
|
#include "template_parser.h"
|
||||||
|
#include "template_utils.h"
|
||||||
|
#include "template_lmo.h"
|
||||||
|
|
||||||
|
#define TEMPLATE_LUALIB_META "gluon.web.template.parser"
|
||||||
|
|
||||||
|
LUALIB_API int luaopen_gluon_web_template_parser(lua_State *L);
|
||||||
|
|
||||||
|
#endif
|
419
package/gluon-web/src/template_parser.c
Normal file
419
package/gluon-web/src/template_parser.c
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* LuCI Template - Parser implementation
|
||||||
|
*
|
||||||
|
* 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_parser.h"
|
||||||
|
#include "template_utils.h"
|
||||||
|
#include "template_lmo.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* leading and trailing code for different types */
|
||||||
|
static const char *const gen_code[9][2] = {
|
||||||
|
{NULL, NULL},
|
||||||
|
{"write(\"", "\")"},
|
||||||
|
{NULL, NULL},
|
||||||
|
{"write(tostring(", " or \"\"))"},
|
||||||
|
{"include(\"", "\")"},
|
||||||
|
{"write(\"", "\")"},
|
||||||
|
{"write(\"", "\")"},
|
||||||
|
{NULL, " "},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Simple strstr() like function that takes len arguments for both haystack and needle. */
|
||||||
|
static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
|
||||||
|
{
|
||||||
|
int match = 0;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
for( i = 0; i < hslen; i++ )
|
||||||
|
{
|
||||||
|
if( haystack[i] == needle[0] )
|
||||||
|
{
|
||||||
|
match = ((ndlen == 1) || ((i + ndlen) <= hslen));
|
||||||
|
|
||||||
|
for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ )
|
||||||
|
{
|
||||||
|
if( haystack[i+j] != needle[j] )
|
||||||
|
{
|
||||||
|
match = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( match )
|
||||||
|
return &haystack[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct template_parser * template_open(const char *file)
|
||||||
|
{
|
||||||
|
struct stat s;
|
||||||
|
struct template_parser *parser;
|
||||||
|
|
||||||
|
if (!(parser = malloc(sizeof(*parser))))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
memset(parser, 0, sizeof(*parser));
|
||||||
|
parser->fd = -1;
|
||||||
|
parser->file = file;
|
||||||
|
|
||||||
|
if (stat(file, &s))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if ((parser->fd = open(file, O_RDONLY)) < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
parser->size = s.st_size;
|
||||||
|
parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
|
||||||
|
parser->fd, 0);
|
||||||
|
|
||||||
|
if (parser->data != MAP_FAILED)
|
||||||
|
{
|
||||||
|
parser->off = parser->data;
|
||||||
|
parser->cur_chunk.type = T_TYPE_INIT;
|
||||||
|
parser->cur_chunk.s = parser->data;
|
||||||
|
parser->cur_chunk.e = parser->data;
|
||||||
|
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
err:
|
||||||
|
template_close(parser);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct template_parser * template_string(const char *str, uint32_t len)
|
||||||
|
{
|
||||||
|
struct template_parser *parser;
|
||||||
|
|
||||||
|
if (!str) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(parser = malloc(sizeof(*parser))))
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
memset(parser, 0, sizeof(*parser));
|
||||||
|
parser->fd = -1;
|
||||||
|
|
||||||
|
parser->size = len;
|
||||||
|
parser->data = (char*)str;
|
||||||
|
|
||||||
|
parser->off = parser->data;
|
||||||
|
parser->cur_chunk.type = T_TYPE_INIT;
|
||||||
|
parser->cur_chunk.s = parser->data;
|
||||||
|
parser->cur_chunk.e = parser->data;
|
||||||
|
|
||||||
|
return parser;
|
||||||
|
|
||||||
|
err:
|
||||||
|
template_close(parser);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void template_close(struct template_parser *parser)
|
||||||
|
{
|
||||||
|
if (!parser)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parser->gc != NULL)
|
||||||
|
free(parser->gc);
|
||||||
|
|
||||||
|
/* if file is not set, we were parsing a string */
|
||||||
|
if (parser->file) {
|
||||||
|
if ((parser->data != NULL) && (parser->data != MAP_FAILED))
|
||||||
|
munmap(parser->data, parser->size);
|
||||||
|
|
||||||
|
if (parser->fd >= 0)
|
||||||
|
close(parser->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void template_text(struct template_parser *parser, const char *e)
|
||||||
|
{
|
||||||
|
const char *s = parser->off;
|
||||||
|
|
||||||
|
if (s < (parser->data + parser->size))
|
||||||
|
{
|
||||||
|
if (parser->strip_after)
|
||||||
|
{
|
||||||
|
while ((s <= e) && isspace(*s))
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->cur_chunk.type = T_TYPE_TEXT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parser->cur_chunk.type = T_TYPE_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->cur_chunk.line = parser->line;
|
||||||
|
parser->cur_chunk.s = s;
|
||||||
|
parser->cur_chunk.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void template_code(struct template_parser *parser, const char *e)
|
||||||
|
{
|
||||||
|
const char *s = parser->off;
|
||||||
|
|
||||||
|
parser->strip_before = 0;
|
||||||
|
parser->strip_after = 0;
|
||||||
|
|
||||||
|
if (*s == '-')
|
||||||
|
{
|
||||||
|
parser->strip_before = 1;
|
||||||
|
for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*(e-1) == '-')
|
||||||
|
{
|
||||||
|
parser->strip_after = 1;
|
||||||
|
for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (*s)
|
||||||
|
{
|
||||||
|
/* comment */
|
||||||
|
case '#':
|
||||||
|
s++;
|
||||||
|
parser->cur_chunk.type = T_TYPE_COMMENT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* include */
|
||||||
|
case '+':
|
||||||
|
s++;
|
||||||
|
parser->cur_chunk.type = T_TYPE_INCLUDE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* translate */
|
||||||
|
case ':':
|
||||||
|
s++;
|
||||||
|
parser->cur_chunk.type = T_TYPE_I18N;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* translate raw */
|
||||||
|
case '_':
|
||||||
|
s++;
|
||||||
|
parser->cur_chunk.type = T_TYPE_I18N_RAW;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* expr */
|
||||||
|
case '=':
|
||||||
|
s++;
|
||||||
|
parser->cur_chunk.type = T_TYPE_EXPR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* code */
|
||||||
|
default:
|
||||||
|
parser->cur_chunk.type = T_TYPE_CODE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->cur_chunk.line = parser->line;
|
||||||
|
parser->cur_chunk.s = s;
|
||||||
|
parser->cur_chunk.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
template_format_chunk(struct template_parser *parser, size_t *sz)
|
||||||
|
{
|
||||||
|
const char *s, *p;
|
||||||
|
const char *head, *tail;
|
||||||
|
struct template_chunk *c = &parser->prv_chunk;
|
||||||
|
struct template_buffer *buf;
|
||||||
|
|
||||||
|
*sz = 0;
|
||||||
|
s = parser->gc = NULL;
|
||||||
|
|
||||||
|
if (parser->strip_before && c->type == T_TYPE_TEXT)
|
||||||
|
{
|
||||||
|
while ((c->e > c->s) && isspace(*(c->e - 1)))
|
||||||
|
c->e--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* empty chunk */
|
||||||
|
if (c->s == c->e)
|
||||||
|
{
|
||||||
|
if (c->type == T_TYPE_EOF)
|
||||||
|
{
|
||||||
|
*sz = 0;
|
||||||
|
s = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*sz = 1;
|
||||||
|
s = " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* format chunk */
|
||||||
|
else if ((buf = buf_init(c->e - c->s)) != NULL)
|
||||||
|
{
|
||||||
|
if ((head = gen_code[c->type][0]) != NULL)
|
||||||
|
buf_append(buf, head, strlen(head));
|
||||||
|
|
||||||
|
switch (c->type)
|
||||||
|
{
|
||||||
|
case T_TYPE_TEXT:
|
||||||
|
luastr_escape(buf, c->s, c->e - c->s, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_TYPE_EXPR:
|
||||||
|
buf_append(buf, c->s, c->e - c->s);
|
||||||
|
for (p = c->s; p < c->e; p++)
|
||||||
|
parser->line += (*p == '\n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_TYPE_INCLUDE:
|
||||||
|
luastr_escape(buf, c->s, c->e - c->s, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_TYPE_I18N:
|
||||||
|
luastr_translate(buf, c->s, c->e - c->s, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_TYPE_I18N_RAW:
|
||||||
|
luastr_translate(buf, c->s, c->e - c->s, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_TYPE_CODE:
|
||||||
|
buf_append(buf, c->s, c->e - c->s);
|
||||||
|
for (p = c->s; p < c->e; p++)
|
||||||
|
parser->line += (*p == '\n');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tail = gen_code[c->type][1]) != NULL)
|
||||||
|
buf_append(buf, tail, strlen(tail));
|
||||||
|
|
||||||
|
*sz = buf_length(buf);
|
||||||
|
s = parser->gc = buf_destroy(buf);
|
||||||
|
|
||||||
|
if (!*sz)
|
||||||
|
{
|
||||||
|
*sz = 1;
|
||||||
|
s = " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *template_reader(lua_State *L, void *ud, size_t *sz)
|
||||||
|
{
|
||||||
|
struct template_parser *parser = ud;
|
||||||
|
int rem = parser->size - (parser->off - parser->data);
|
||||||
|
char *tag;
|
||||||
|
|
||||||
|
parser->prv_chunk = parser->cur_chunk;
|
||||||
|
|
||||||
|
/* free previous string */
|
||||||
|
if (parser->gc)
|
||||||
|
{
|
||||||
|
free(parser->gc);
|
||||||
|
parser->gc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* before tag */
|
||||||
|
if (!parser->in_expr)
|
||||||
|
{
|
||||||
|
if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
|
||||||
|
{
|
||||||
|
template_text(parser, tag);
|
||||||
|
parser->off = tag + 2;
|
||||||
|
parser->in_expr = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
template_text(parser, parser->data + parser->size);
|
||||||
|
parser->off = parser->data + parser->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inside tag */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
|
||||||
|
{
|
||||||
|
template_code(parser, tag);
|
||||||
|
parser->off = tag + 2;
|
||||||
|
parser->in_expr = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* unexpected EOF */
|
||||||
|
template_code(parser, parser->data + parser->size);
|
||||||
|
|
||||||
|
*sz = 1;
|
||||||
|
return "\033";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return template_format_chunk(parser, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
int template_error(lua_State *L, struct template_parser *parser)
|
||||||
|
{
|
||||||
|
const char *err = luaL_checkstring(L, -1);
|
||||||
|
const char *off = parser->prv_chunk.s;
|
||||||
|
const char *ptr;
|
||||||
|
char msg[1024];
|
||||||
|
int line = 0;
|
||||||
|
int chunkline = 0;
|
||||||
|
|
||||||
|
if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
|
||||||
|
{
|
||||||
|
chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
|
||||||
|
|
||||||
|
while (*ptr)
|
||||||
|
{
|
||||||
|
if (*ptr++ == ' ')
|
||||||
|
{
|
||||||
|
err = ptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
|
||||||
|
{
|
||||||
|
off = parser->data + parser->size;
|
||||||
|
err = "'%>' expected before end of file";
|
||||||
|
chunkline = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ptr = parser->data; ptr < off; ptr++)
|
||||||
|
if (*ptr == '\n')
|
||||||
|
line++;
|
||||||
|
|
||||||
|
snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
|
||||||
|
parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)");
|
||||||
|
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushinteger(L, line + chunkline);
|
||||||
|
lua_pushstring(L, msg);
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
80
package/gluon-web/src/template_parser.h
Normal file
80
package/gluon-web/src/template_parser.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* LuCI Template - Parser header
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TEMPLATE_PARSER_H_
|
||||||
|
#define _TEMPLATE_PARSER_H_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* code types */
|
||||||
|
#define T_TYPE_INIT 0
|
||||||
|
#define T_TYPE_TEXT 1
|
||||||
|
#define T_TYPE_COMMENT 2
|
||||||
|
#define T_TYPE_EXPR 3
|
||||||
|
#define T_TYPE_INCLUDE 4
|
||||||
|
#define T_TYPE_I18N 5
|
||||||
|
#define T_TYPE_I18N_RAW 6
|
||||||
|
#define T_TYPE_CODE 7
|
||||||
|
#define T_TYPE_EOF 8
|
||||||
|
|
||||||
|
|
||||||
|
struct template_chunk {
|
||||||
|
const char *s;
|
||||||
|
const char *e;
|
||||||
|
int type;
|
||||||
|
int line;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* parser state */
|
||||||
|
struct template_parser {
|
||||||
|
int fd;
|
||||||
|
uint32_t size;
|
||||||
|
char *data;
|
||||||
|
char *off;
|
||||||
|
char *gc;
|
||||||
|
int line;
|
||||||
|
int in_expr;
|
||||||
|
int strip_before;
|
||||||
|
int strip_after;
|
||||||
|
struct template_chunk prv_chunk;
|
||||||
|
struct template_chunk cur_chunk;
|
||||||
|
const char *file;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct template_parser * template_open(const char *file);
|
||||||
|
struct template_parser * template_string(const char *str, uint32_t len);
|
||||||
|
void template_close(struct template_parser *parser);
|
||||||
|
|
||||||
|
const char *template_reader(lua_State *L, void *ud, size_t *sz);
|
||||||
|
int template_error(lua_State *L, struct template_parser *parser);
|
||||||
|
|
||||||
|
#endif
|
384
package/gluon-web/src/template_utils.c
Normal file
384
package/gluon-web/src/template_utils.c
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
* LuCI Template - Utility functions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2010 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_utils.h"
|
||||||
|
#include "template_lmo.h"
|
||||||
|
|
||||||
|
/* initialize a buffer object */
|
||||||
|
struct template_buffer * buf_init(int size)
|
||||||
|
{
|
||||||
|
struct template_buffer *buf;
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
size = 1024;
|
||||||
|
|
||||||
|
buf = (struct template_buffer *)malloc(sizeof(struct template_buffer));
|
||||||
|
|
||||||
|
if (buf != NULL)
|
||||||
|
{
|
||||||
|
buf->fill = 0;
|
||||||
|
buf->size = size;
|
||||||
|
buf->data = malloc(buf->size);
|
||||||
|
|
||||||
|
if (buf->data != NULL)
|
||||||
|
{
|
||||||
|
buf->dptr = buf->data;
|
||||||
|
buf->data[0] = 0;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* grow buffer */
|
||||||
|
static int buf_grow(struct template_buffer *buf, int size)
|
||||||
|
{
|
||||||
|
unsigned int off = (buf->dptr - buf->data);
|
||||||
|
char *data;
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
size = 1024;
|
||||||
|
|
||||||
|
data = realloc(buf->data, buf->size + size);
|
||||||
|
|
||||||
|
if (data != NULL)
|
||||||
|
{
|
||||||
|
buf->data = data;
|
||||||
|
buf->dptr = data + off;
|
||||||
|
buf->size += size;
|
||||||
|
|
||||||
|
return buf->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* put one char into buffer object */
|
||||||
|
static int buf_putchar(struct template_buffer *buf, char c)
|
||||||
|
{
|
||||||
|
if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) )
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
*(buf->dptr++) = c;
|
||||||
|
*(buf->dptr) = 0;
|
||||||
|
|
||||||
|
buf->fill++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* append data to buffer */
|
||||||
|
int buf_append(struct template_buffer *buf, const char *s, int len)
|
||||||
|
{
|
||||||
|
if ((buf->fill + len + 1) >= buf->size)
|
||||||
|
{
|
||||||
|
if (!buf_grow(buf, len + 1))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf->dptr, s, len);
|
||||||
|
buf->fill += len;
|
||||||
|
buf->dptr += len;
|
||||||
|
|
||||||
|
*(buf->dptr) = 0;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* destroy buffer object and return pointer to data */
|
||||||
|
char * buf_destroy(struct template_buffer *buf)
|
||||||
|
{
|
||||||
|
char *data = buf->data;
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* calculate the number of expected continuation chars */
|
||||||
|
static inline int mb_num_chars(unsigned char c)
|
||||||
|
{
|
||||||
|
if ((c & 0xE0) == 0xC0)
|
||||||
|
return 2;
|
||||||
|
else if ((c & 0xF0) == 0xE0)
|
||||||
|
return 3;
|
||||||
|
else if ((c & 0xF8) == 0xF0)
|
||||||
|
return 4;
|
||||||
|
else if ((c & 0xFC) == 0xF8)
|
||||||
|
return 5;
|
||||||
|
else if ((c & 0xFE) == 0xFC)
|
||||||
|
return 6;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test whether the given byte is a valid continuation char */
|
||||||
|
static inline int mb_is_cont(unsigned char c)
|
||||||
|
{
|
||||||
|
return ((c >= 0x80) && (c <= 0xBF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test whether the byte sequence at the given pointer with the given
|
||||||
|
* length is the shortest possible representation of the code point */
|
||||||
|
static inline int mb_is_shortest(unsigned char *s, int n)
|
||||||
|
{
|
||||||
|
switch (n)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
/* 1100000x (10xxxxxx) */
|
||||||
|
return !(((*s >> 1) == 0x60) &&
|
||||||
|
((*(s+1) >> 6) == 0x02));
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
/* 11100000 100xxxxx (10xxxxxx) */
|
||||||
|
return !((*s == 0xE0) &&
|
||||||
|
((*(s+1) >> 5) == 0x04) &&
|
||||||
|
((*(s+2) >> 6) == 0x02));
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
/* 11110000 1000xxxx (10xxxxxx 10xxxxxx) */
|
||||||
|
return !((*s == 0xF0) &&
|
||||||
|
((*(s+1) >> 4) == 0x08) &&
|
||||||
|
((*(s+2) >> 6) == 0x02) &&
|
||||||
|
((*(s+3) >> 6) == 0x02));
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
/* 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) */
|
||||||
|
return !((*s == 0xF8) &&
|
||||||
|
((*(s+1) >> 3) == 0x10) &&
|
||||||
|
((*(s+2) >> 6) == 0x02) &&
|
||||||
|
((*(s+3) >> 6) == 0x02) &&
|
||||||
|
((*(s+4) >> 6) == 0x02));
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
/* 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) */
|
||||||
|
return !((*s == 0xF8) &&
|
||||||
|
((*(s+1) >> 2) == 0x20) &&
|
||||||
|
((*(s+2) >> 6) == 0x02) &&
|
||||||
|
((*(s+3) >> 6) == 0x02) &&
|
||||||
|
((*(s+4) >> 6) == 0x02) &&
|
||||||
|
((*(s+5) >> 6) == 0x02));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test whether the byte sequence at the given pointer with the given
|
||||||
|
* length is an UTF-16 surrogate */
|
||||||
|
static inline int mb_is_surrogate(unsigned char *s, int n)
|
||||||
|
{
|
||||||
|
return ((n == 3) && (*s == 0xED) && (*(s+1) >= 0xA0) && (*(s+1) <= 0xBF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test whether the byte sequence at the given pointer with the given
|
||||||
|
* length is an illegal UTF-8 code point */
|
||||||
|
static inline int mb_is_illegal(unsigned char *s, int n)
|
||||||
|
{
|
||||||
|
return ((n == 3) && (*s == 0xEF) && (*(s+1) == 0xBF) &&
|
||||||
|
(*(s+2) >= 0xBE) && (*(s+2) <= 0xBF));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* scan given source string, validate UTF-8 sequence and store result
|
||||||
|
* in given buffer object */
|
||||||
|
static int validate_utf8(unsigned char **s, int l, struct template_buffer *buf)
|
||||||
|
{
|
||||||
|
unsigned char *ptr = *s;
|
||||||
|
unsigned int o = 0, v, n;
|
||||||
|
|
||||||
|
/* ascii byte without null */
|
||||||
|
if ((*(ptr+0) >= 0x01) && (*(ptr+0) <= 0x7F))
|
||||||
|
{
|
||||||
|
if (!buf_putchar(buf, *ptr++))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
o = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* multi byte sequence */
|
||||||
|
else if ((n = mb_num_chars(*ptr)) > 1)
|
||||||
|
{
|
||||||
|
/* count valid chars */
|
||||||
|
for (v = 1; (v <= n) && ((o+v) < l) && mb_is_cont(*(ptr+v)); v++);
|
||||||
|
|
||||||
|
switch (n)
|
||||||
|
{
|
||||||
|
case 6:
|
||||||
|
case 5:
|
||||||
|
/* five and six byte sequences are always invalid */
|
||||||
|
if (!buf_putchar(buf, '?'))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* if the number of valid continuation bytes matches the
|
||||||
|
* expected number and if the sequence is legal, copy
|
||||||
|
* the bytes to the destination buffer */
|
||||||
|
if ((v == n) && mb_is_shortest(ptr, n) &&
|
||||||
|
!mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n))
|
||||||
|
{
|
||||||
|
/* copy sequence */
|
||||||
|
if (!buf_append(buf, (char *)ptr, n))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the found sequence is illegal, skip it */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* invalid sequence */
|
||||||
|
if (!buf_putchar(buf, '?'))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* advance beyound the last found valid continuation char */
|
||||||
|
o = v;
|
||||||
|
ptr += v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* invalid byte (0x00) */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!buf_putchar(buf, '?')) /* or 0xEF, 0xBF, 0xBD */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
o = 1;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = ptr;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sanitize given string and strip all invalid XML bytes
|
||||||
|
* Validate UTF-8 sequences
|
||||||
|
* Escape XML control chars */
|
||||||
|
char * pcdata(const char *s, unsigned int l)
|
||||||
|
{
|
||||||
|
struct template_buffer *buf = buf_init(l);
|
||||||
|
unsigned char *ptr = (unsigned char *)s;
|
||||||
|
unsigned int o, v;
|
||||||
|
char esq[8];
|
||||||
|
int esl;
|
||||||
|
|
||||||
|
if (!buf)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (o = 0; o < l; o++)
|
||||||
|
{
|
||||||
|
/* Invalid XML bytes */
|
||||||
|
if (((*ptr >= 0x00) && (*ptr <= 0x08)) ||
|
||||||
|
((*ptr >= 0x0B) && (*ptr <= 0x0C)) ||
|
||||||
|
((*ptr >= 0x0E) && (*ptr <= 0x1F)) ||
|
||||||
|
(*ptr == 0x7F))
|
||||||
|
{
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Escapes */
|
||||||
|
else if ((*ptr == 0x26) ||
|
||||||
|
(*ptr == 0x27) ||
|
||||||
|
(*ptr == 0x22) ||
|
||||||
|
(*ptr == 0x3C) ||
|
||||||
|
(*ptr == 0x3E))
|
||||||
|
{
|
||||||
|
esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
|
||||||
|
|
||||||
|
if (!buf_append(buf, esq, esl))
|
||||||
|
break;
|
||||||
|
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ascii char */
|
||||||
|
else if (*ptr <= 0x7F)
|
||||||
|
{
|
||||||
|
buf_putchar(buf, (char)*ptr++);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* multi byte sequence */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!(v = validate_utf8(&ptr, l - o, buf)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
o += (v - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf_destroy(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, int escape_xml)
|
||||||
|
{
|
||||||
|
int esl;
|
||||||
|
char esq[8];
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
for (ptr = (char *)s; ptr < (s + l); ptr++)
|
||||||
|
{
|
||||||
|
switch (*ptr)
|
||||||
|
{
|
||||||
|
case '\\':
|
||||||
|
buf_append(out, "\\\\", 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
if (escape_xml)
|
||||||
|
buf_append(out, """, 5);
|
||||||
|
else
|
||||||
|
buf_append(out, "\\\"", 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
buf_append(out, "\\n", 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\'':
|
||||||
|
case '&':
|
||||||
|
case '<':
|
||||||
|
case '>':
|
||||||
|
if (escape_xml)
|
||||||
|
{
|
||||||
|
esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
|
||||||
|
buf_append(out, esq, esl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
buf_putchar(out, *ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, int escape_xml)
|
||||||
|
{
|
||||||
|
char *tr;
|
||||||
|
int trlen;
|
||||||
|
|
||||||
|
if (!lmo_translate(s, l, &tr, &trlen))
|
||||||
|
luastr_escape(out, tr, trlen, escape_xml);
|
||||||
|
else
|
||||||
|
luastr_escape(out, s, l, escape_xml);
|
||||||
|
}
|
51
package/gluon-web/src/template_utils.h
Normal file
51
package/gluon-web/src/template_utils.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* LuCI Template - Utility header
|
||||||
|
*
|
||||||
|
* Copyright (C) 2010-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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TEMPLATE_UTILS_H_
|
||||||
|
#define _TEMPLATE_UTILS_H_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* buffer object */
|
||||||
|
struct template_buffer {
|
||||||
|
char *data;
|
||||||
|
char *dptr;
|
||||||
|
unsigned int size;
|
||||||
|
unsigned int fill;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct template_buffer * buf_init(int size);
|
||||||
|
int buf_append(struct template_buffer *buf, const char *s, int len);
|
||||||
|
char * buf_destroy(struct template_buffer *buf);
|
||||||
|
|
||||||
|
/* read buffer length */
|
||||||
|
static inline int buf_length(struct template_buffer *buf)
|
||||||
|
{
|
||||||
|
return buf->fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char * pcdata(const char *s, unsigned int l);
|
||||||
|
|
||||||
|
void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
|
||||||
|
void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
|
||||||
|
|
||||||
|
#endif
|
@ -17,6 +17,8 @@ endef
|
|||||||
|
|
||||||
# Languages supported by LuCi
|
# Languages supported by LuCi
|
||||||
GLUON_SUPPORTED_LANGS := ca cs de el en es fr he hu it ja ms no pl pt-br pt ro ru sk sv tr uk vi zh-cn zh-tw
|
GLUON_SUPPORTED_LANGS := ca cs de el en es fr he hu it ja ms no pl pt-br pt ro ru sk sv tr uk vi zh-cn zh-tw
|
||||||
|
GLUON_LANG_de := German
|
||||||
|
GLUON_LANG_fr := French
|
||||||
|
|
||||||
GLUON_I18N_PACKAGES := $(foreach lang,$(GLUON_SUPPORTED_LANGS),+LUCI_LANG_$(lang):luci-i18n-base-$(lang))
|
GLUON_I18N_PACKAGES := $(foreach lang,$(GLUON_SUPPORTED_LANGS),+LUCI_LANG_$(lang):luci-i18n-base-$(lang))
|
||||||
GLUON_I18N_CONFIG := $(foreach lang,$(GLUON_SUPPORTED_LANGS),CONFIG_LUCI_LANG_$(lang))
|
GLUON_I18N_CONFIG := $(foreach lang,$(GLUON_SUPPORTED_LANGS),CONFIG_LUCI_LANG_$(lang))
|
||||||
|
Loading…
Reference in New Issue
Block a user