1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/.hgignore Mon Feb 12 02:14:11 2007 +0100
1.3 @@ -0,0 +1,4 @@
1.4 +var
1.5 +content
1.6 +htdocs/images
1.7 +htdocs/download
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/COPYRIGHT Mon Feb 12 02:14:11 2007 +0100
2.3 @@ -0,0 +1,21 @@
2.4 +
2.5 +Copyright (C) 2007 by Timm S. Mueller <tmueller at neoscientists.org>
2.6 +
2.7 +Permission is hereby granted, free of charge, to any person obtaining a
2.8 +copy of this software and associated documentation files (the "Software"),
2.9 +to deal in the Software without restriction, including without limitation
2.10 +the rights to use, copy, modify, merge, publish, distribute, sublicense,
2.11 +and/or sell copies of the Software, and to permit persons to whom the
2.12 +Software is furnished to do so, subject to the following conditions:
2.13 +
2.14 +The above copyright notice and this permission notice shall be included in
2.15 +all copies or substantial portions of the Software.
2.16 +
2.17 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2.18 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2.19 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2.20 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2.21 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2.22 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2.23 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2.24 +
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/Makefile Mon Feb 12 02:14:11 2007 +0100
3.3 @@ -0,0 +1,52 @@
3.4 +
3.5 +WWWUSER ?= apache
3.6 +GROUP ?= apache
3.7 +LANGUAGE ?= en
3.8 +
3.9 +CONTENTDIR ?= content
3.10 +VARDIR ?= var
3.11 +SESSIONDIR ?= $(VARDIR)/sessions
3.12 +CGIDIR ?= cgi-bin
3.13 +ETCDIR ?= etc
3.14 +
3.15 +help:
3.16 + @echo
3.17 + @echo "Loona make targets"
3.18 + @echo "-------------------------------------------------------------------------------"
3.19 + @echo "help .......... This help"
3.20 + @echo "modules ....... Build modules"
3.21 + @echo "setup ......... Create initial site structure [LANGUAGE: $(LANGUAGE)]"
3.22 + @echo "permissions ... Set permissions [WWWUSER: $(WWWUSER), GROUP: $(GROUP)]"
3.23 + @echo "all ........... All of the above: modules, setup, permissions."
3.24 + @echo
3.25 + @echo "NOTE .......... Set environment variables to complement your setup, e.g."
3.26 + @echo " % LANGUAGE=de WWWUSER=wwwrun make help"
3.27 + @echo
3.28 +
3.29 +clean install modules:
3.30 + $(MAKE) -C cgi-bin $@
3.31 +
3.32 +setup:
3.33 + mkdir -p $(CONTENTDIR)
3.34 + -cd $(CONTENTDIR) && mkdir default_$(LANGUAGE)
3.35 + cd $(CONTENTDIR) && ln -snf default_$(LANGUAGE) current_$(LANGUAGE)
3.36 + mkdir -p $(SESSIONDIR)
3.37 + if test ! -f $(CONTENTDIR)/current_$(LANGUAGE)/.sections; then echo '[1] = { name = "login" }' >> $(CONTENTDIR)/current_$(LANGUAGE)/.sections; fi
3.38 + if test ! -f $(CONTENTDIR)/current_$(LANGUAGE)/login; then echo 'INCLUDE(login)' >> $(CONTENTDIR)/current_$(LANGUAGE)/login; fi
3.39 +
3.40 +permissions:
3.41 + chmod -R 664 *
3.42 + chown -R :$(GROUP) *
3.43 + find . -type d | xargs chmod ugo+x
3.44 + chmod ugo+x $(CGIDIR)/weblua.cgi
3.45 + chown -R $(WWWUSER) $(SESSIONDIR)
3.46 + chown -R $(WWWUSER) $(CONTENTDIR)
3.47 + find . -name CVS -type d | xargs -r chmod g+rw
3.48 + chown $(WWWUSER):$(GROUP) $(ETCDIR)/passwd.lua
3.49 + chmod 460 $(ETCDIR)/passwd.lua
3.50 +
3.51 +all: modules setup permissions
3.52 +
3.53 +distclean:
3.54 + -rm -Rf $(CONTENTDIR)
3.55 + -rm -Rf $(VARDIR)
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/cgi-bin/Makefile Mon Feb 12 02:14:11 2007 +0100
4.3 @@ -0,0 +1,54 @@
4.4 +
4.5 +INCL = -I.
4.6 +WARN = -Wall
4.7 +#DEBUG = -g
4.8 +OPT = -O2
4.9 +
4.10 +INSTPATH ?= /usr/local/lib/lua/5.1
4.11 +
4.12 +help:
4.13 + @echo
4.14 + @echo "'tek' module targets"
4.15 + @echo "-------------------------------------------------------------------------------"
4.16 + @echo "help .......... This help"
4.17 + @echo "modules ....... Build modules"
4.18 + @echo "install ....... Install modules [INSTPATH: $(INSTPATH)]"
4.19 + @echo
4.20 + @echo "NOTE .......... Modules can reside locally under cgi-bin."
4.21 + @echo " An installation is not strictly required."
4.22 + @echo
4.23 +
4.24 +.c.o:
4.25 + $(CC) $(INCL) $(WARN) $(DEBUG) $(OPT) -fPIC -DPIC -c $? -o $@
4.26 +
4.27 +modules: tek/web/include.so tek/posix.so
4.28 +
4.29 +all: modules
4.30 +
4.31 +install: tek/web/include.so tek/posix.so
4.32 + -install -d $(INSTPATH)/tek/cgi/header $(INSTPATH)/tek/cgi/request $(INSTPATH)/tek/web
4.33 + -luac -s -o $(INSTPATH)/tek.lua tek.lua
4.34 + -luac -s -o $(INSTPATH)/tek/cgi.lua tek/cgi.lua
4.35 + -luac -s -o $(INSTPATH)/tek/util.lua tek/util.lua
4.36 + -luac -s -o $(INSTPATH)/tek/web.lua tek/web.lua
4.37 + -luac -s -o $(INSTPATH)/tek/cgi/document.lua tek/cgi/document.lua
4.38 + -luac -s -o $(INSTPATH)/tek/cgi/header.lua tek/cgi/header.lua
4.39 + -luac -s -o $(INSTPATH)/tek/cgi/request.lua tek/cgi/request.lua
4.40 + -luac -s -o $(INSTPATH)/tek/cgi/session.lua tek/cgi/session.lua
4.41 + -luac -s -o $(INSTPATH)/tek/web/markup.lua tek/web/markup.lua
4.42 + -luac -s -o $(INSTPATH)/tek/cgi/header/cookies.lua tek/cgi/header/cookies.lua
4.43 + -luac -s -o $(INSTPATH)/tek/cgi/request/args.lua tek/cgi/request/args.lua
4.44 + -luac -s -o $(INSTPATH)/tek/web/markup.lua tek/web/markup.lua
4.45 + -install -s tek/*.so $(INSTPATH)/tek
4.46 + -install -s tek/web/*.so $(INSTPATH)/tek/web
4.47 +
4.48 +
4.49 +tek/web/include.so: tek/web/include.o
4.50 + $(CC) $^ -shared -o $@ $(LIBS)
4.51 +
4.52 +tek/posix.so: tek/posix.o
4.53 + $(CC) $^ -shared -o $@ $(LIBS)
4.54 +
4.55 +clean:
4.56 + -rm tek/*.o tek/*.so
4.57 + -rm tek/web/*.o tek/web/*.so
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/cgi-bin/cgilua/post.lua Mon Feb 12 02:14:11 2007 +0100
5.3 @@ -0,0 +1,293 @@
5.4 +----------------------------------------------------------------------------
5.5 +-- Process POST data.
5.6 +-- This library depends on some functions that read POST data and other
5.7 +-- HTTP information. A beginning is:
5.8 +-- require"post"
5.9 +-- local params = {}
5.10 +-- post.parsedata {
5.11 +-- read = ap.get_client_block or io.input,
5.12 +-- discardinput = ap.discard_request_body,
5.13 +-- content_type = ap.get_header"content-type" or os.getenv"CONTENT_TYPE",
5.14 +-- content_length = ap.get_header"content-length" or os.getenv"CONTENT_LENGTH",
5.15 +-- maxinput = 1024 * 1024,
5.16 +-- maxfilesize = 512 * 1024,
5.17 +-- args = params,
5.18 +-- }
5.19 +----------------------------------------------------------------------------
5.20 +-- $Id: post.lua,v 1.7 2005/04/29 01:01:11 mascarenhas Exp $
5.21 +----------------------------------------------------------------------------
5.22 +
5.23 +require"cgilua.readuntil"
5.24 +require"cgilua.urlcode"
5.25 +
5.26 +local assert, error, tonumber, tostring, type = assert, error, tonumber, tostring, type
5.27 +local tmpfile = io.tmpfile
5.28 +local getn, tinsert = table.getn, table.insert
5.29 +local format, gsub, strfind, strlower, strlen = string.format, string.gsub, string.find, string.lower, string.len
5.30 +local min = math.min
5.31 +local _pairs = pairs
5.32 +
5.33 +local iterate = cgilua.readuntil.iterate
5.34 +local urlcode = cgilua.urlcode
5.35 +
5.36 +-- environment for processing multipart/form-data input
5.37 +local boundary = nil -- boundary string that separates each 'part' of input
5.38 +local maxfilesize = nil -- maximum size for file upload
5.39 +local maxinput = nil -- maximum size of total POST data
5.40 +local inputfile = nil -- temporary file for inputting form-data
5.41 +local bytesleft = nil -- number of bytes yet to be read
5.42 +local content_type = nil -- request's content-type
5.43 +-- local functions
5.44 +local discardinput = nil -- discard all remaining input
5.45 +local readuntil = nil -- read until delimiter
5.46 +local read = nil -- basic read function
5.47 +
5.48 +module "cgilua.post"
5.49 +
5.50 +----------------------------------------------------------------------------
5.51 +-- Extract the boundary string from CONTENT_TYPE metavariable
5.52 +----------------------------------------------------------------------------
5.53 +local function getboundary ()
5.54 + local _,_,boundary = strfind (content_type, "boundary%=(.-)$")
5.55 + return "--"..boundary
5.56 +end
5.57 +
5.58 +----------------------------------------------------------------------------
5.59 +-- Create a table containing the headers of a multipart/form-data field
5.60 +----------------------------------------------------------------------------
5.61 +local function breakheaders (hdrdata)
5.62 + local headers = {}
5.63 + gsub (hdrdata, '([^%c%s:]+):%s+([^\n]+)', function(type,val)
5.64 + type = strlower(type)
5.65 + headers[type] = val
5.66 + end)
5.67 + return headers
5.68 +end
5.69 +
5.70 +----------------------------------------------------------------------------
5.71 +-- Read the headers of the next multipart/form-data field
5.72 +--
5.73 +-- This function returns a table containing the headers values. Each header
5.74 +-- value is indexed by the corresponding header "type".
5.75 +-- If end of input is reached (no more fields to process) it returns nil.
5.76 +----------------------------------------------------------------------------
5.77 +local function readfieldheaders ()
5.78 + local EOH = "\r\n\r\n" -- <CR><LF><CR><LF>
5.79 + local hdrdata = ""
5.80 + local out = function (str) hdrdata = hdrdata..str end
5.81 + if readuntil (EOH, out) then
5.82 + -- parse headers
5.83 + return breakheaders (hdrdata)
5.84 + else
5.85 + -- no header found
5.86 + return nil
5.87 + end
5.88 +end
5.89 +
5.90 +----------------------------------------------------------------------------
5.91 +-- Extract a field name (and possible filename) from its disposition header
5.92 +----------------------------------------------------------------------------
5.93 +local function getfieldnames (headers)
5.94 + local disposition_hdr = headers["content-disposition"]
5.95 + local attrs = {}
5.96 + if disposition_hdr then
5.97 + gsub(disposition_hdr, ';%s*([^%s=]+)="(.-)"', function(attr, val)
5.98 + attrs[attr] = val
5.99 + end)
5.100 + else
5.101 + error("Error processing multipart/form-data."..
5.102 + "\nMissing content-disposition header")
5.103 + end
5.104 + return attrs.name, attrs.filename
5.105 +end
5.106 +
5.107 +----------------------------------------------------------------------------
5.108 +-- Read the contents of a 'regular' field to a string
5.109 +----------------------------------------------------------------------------
5.110 +local function readfieldcontents ()
5.111 + local value = ""
5.112 + local boundaryline = "\r\n"..boundary
5.113 + local out = function (str) value = value..str end
5.114 + if readuntil (boundaryline, out) then
5.115 + return value
5.116 + else
5.117 + error("Error processing multipart/form-data.\nUnexpected end of input\n")
5.118 + end
5.119 +end
5.120 +
5.121 +----------------------------------------------------------------------------
5.122 +-- Read the contents of a 'file' field to a temporary file (file upload)
5.123 +----------------------------------------------------------------------------
5.124 +local function fileupload (filename)
5.125 + -- create a temporary file for uploading the file field
5.126 + local file, err = tmpfile()
5.127 + if file == nil then
5.128 + discardinput(bytesleft)
5.129 + error("Cannot create a temporary file.\n"..err)
5.130 + end
5.131 + local bytesread = 0
5.132 + local boundaryline = "\r\n"..boundary
5.133 + local out = function (str)
5.134 + local sl = strlen (str)
5.135 + if bytesread + sl > maxfilesize then
5.136 + discardinput (bytesleft)
5.137 + error (format ("Maximum file size (%d kbytes) exceeded while uploading `%s'", maxfilesize / 1024, filename))
5.138 + end
5.139 + file:write (str)
5.140 + bytesread = bytesread + sl
5.141 + end
5.142 + if readuntil (boundaryline, out) then
5.143 + file:seek ("set", 0)
5.144 + return file, bytesread
5.145 + else
5.146 + error (format ("Error processing multipart/form-data.\nUnexpected end of input while uploading %s", filename))
5.147 + end
5.148 +end
5.149 +
5.150 +----------------------------------------------------------------------------
5.151 +-- Compose a file field 'value'
5.152 +----------------------------------------------------------------------------
5.153 +local function filevalue (filehandle, filename, filesize, headers)
5.154 + -- the temporary file handle
5.155 + local value = { file = filehandle,
5.156 + filename = filename,
5.157 + filesize = filesize }
5.158 + -- copy additional header values
5.159 + for hdr, hdrval in _pairs(headers) do
5.160 + if hdr ~= "content-disposition" then
5.161 + value[hdr] = hdrval
5.162 + end
5.163 + end
5.164 + return value
5.165 +end
5.166 +
5.167 +----------------------------------------------------------------------------
5.168 +-- Process multipart/form-data
5.169 +--
5.170 +-- This function receives the total size of the incoming multipart/form-data,
5.171 +-- the maximum size for a file upload, and a reference to a table where the
5.172 +-- form fields should be stored.
5.173 +--
5.174 +-- For every field in the incoming form-data a (name=value) pair is
5.175 +-- inserted into the given table. [[name]] is the field name extracted
5.176 +-- from the content-disposition header.
5.177 +--
5.178 +-- If a field is of type 'file' (i.e., a 'filename' attribute was found
5.179 +-- in its content-disposition header) a temporary file is created
5.180 +-- and the field contents are written to it. In this case,
5.181 +-- [[value]] has a table that contains the temporary file handle
5.182 +-- (key 'file') and the file name (key 'filename'). Optional headers
5.183 +-- included in the field description are also inserted into this table,
5.184 +-- as (header_type=value) pairs.
5.185 +--
5.186 +-- If the field is not of type 'file', [[value]] contains the field
5.187 +-- contents.
5.188 +----------------------------------------------------------------------------
5.189 +local function Main (inputsize, args)
5.190 +
5.191 + -- create a temporary file for processing input data
5.192 + local inputf,err = tmpfile()
5.193 + if inputf == nil then
5.194 + discardinput(inputsize)
5.195 + error("Cannot create a temporary file.\n"..err)
5.196 + end
5.197 +
5.198 + -- set the environment for processing the multipart/form-data
5.199 + inputfile = inputf
5.200 + bytesleft = inputsize
5.201 + maxfilesize = maxfilesize or inputsize
5.202 + boundary = getboundary()
5.203 +
5.204 + while true do
5.205 + -- read the next field header(s)
5.206 + local headers = readfieldheaders()
5.207 + if not headers then break end -- end of input
5.208 +
5.209 + -- get the name attributes for the form field (name and filename)
5.210 + local name, filename = getfieldnames(headers)
5.211 +
5.212 + -- get the field contents
5.213 + local value
5.214 + if filename then
5.215 + local filehandle, filesize = fileupload(filename)
5.216 + value = filevalue(filehandle, filename, filesize, headers)
5.217 + else
5.218 + value = readfieldcontents()
5.219 + end
5.220 +
5.221 + -- insert the form field into table [[args]]
5.222 + urlcode.insertfield(args, name, value)
5.223 + end
5.224 +end
5.225 +
5.226 +---------------------------------------------------------------------------
5.227 +-- Initialize the library by setting the dependent functions:
5.228 +-- content_type = value of "Content-type" header
5.229 +-- content_length = value of "Content-length" header
5.230 +-- read = function that can read POST data
5.231 +-- discardinput (optional) = function that discard POST data
5.232 +-- maxinput (optional) = limit of POST data (in bytes)
5.233 +-- maxfilesize (optional) = limit of uploaded file(s) (in bytes)
5.234 +---------------------------------------------------------------------------
5.235 +local function init (defs)
5.236 + assert (defs.read)
5.237 + assert (defs.content_type)
5.238 + read = defs.read
5.239 + readuntil = iterate (function ()
5.240 + if bytesleft <= 0 then return nil end
5.241 + local n = min (bytesleft, 2^13) -- 2^13 == 8192
5.242 + bytesleft = bytesleft - n
5.243 + return read (n)
5.244 + end)
5.245 + if defs.discard_function then
5.246 + discardinput = defs.discardinput
5.247 + else
5.248 + discardinput = function (inputsize)
5.249 + readuntil ('\0', function()end)
5.250 + end
5.251 + end
5.252 + content_type = defs.content_type
5.253 + if defs.maxinput then
5.254 + maxinput = defs.maxinput
5.255 + end
5.256 + if defs.maxfilesize then
5.257 + maxfilesize = defs.maxfilesize
5.258 + end
5.259 +end
5.260 +
5.261 +----------------------------------------------------------------------------
5.262 +-- Parse the POST REQUEST incoming data according to its "content type"
5.263 +-- as defined by the metavariable CONTENT_TYPE (RFC CGI)
5.264 +--
5.265 +-- An error is issued if the "total" size of the incoming data
5.266 +-- (defined by the metavariable CONTENT_LENGTH) exceeds the
5.267 +-- maximum input size allowed
5.268 +----------------------------------------------------------------------------
5.269 +function parsedata (defs)
5.270 + assert (type(defs.args) == "table", "field `args' must be a table")
5.271 + init (defs)
5.272 + -- get the "total" size of the incoming data
5.273 + local inputsize = tonumber(defs.content_length) or 0
5.274 + if inputsize > maxinput then
5.275 + -- some Web Servers (like IIS) require that all the incoming data is read
5.276 + bytesleft = inputsize
5.277 + discardinput(inputsize)
5.278 + error(format("Total size of incoming data (%d KB) exceeds configured maximum (%d KB)",
5.279 + inputsize /1024, maxinput / 1024))
5.280 + end
5.281 +
5.282 + -- process the incoming data according to its content type
5.283 + local contenttype = content_type
5.284 + if not contenttype then
5.285 + error("Undefined Media Type")
5.286 + end
5.287 + if strfind(contenttype, "x%-www%-form%-urlencoded") then
5.288 + urlcode.parsequery (read (inputsize), defs.args)
5.289 + elseif strfind(contenttype, "multipart/form%-data") then
5.290 + Main (inputsize, defs.args)
5.291 + elseif strfind (contenttype, "text/xml") then
5.292 + tinsert (defs.args, read (inputsize))
5.293 + else
5.294 + error("Unsupported Media Type: "..contenttype)
5.295 + end
5.296 +end
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/cgi-bin/cgilua/readuntil.lua Mon Feb 12 02:14:11 2007 +0100
6.3 @@ -0,0 +1,36 @@
6.4 +----------------------------------------------------------------------------
6.5 +-- $Id: readuntil.lua,v 1.3 2005/03/08 21:04:51 carregal Exp $
6.6 +----------------------------------------------------------------------------
6.7 +
6.8 +local strsub, strfind, strlen = string.sub, string.find, string.len
6.9 +
6.10 +module "cgilua.readuntil"
6.11 +
6.12 +function iterate (inp)
6.13 + local current = ""
6.14 + return function (del, out)
6.15 + local dellen = strlen(del)
6.16 + local i, e
6.17 + while true do
6.18 + i, e = strfind(current, del, 1, 1)
6.19 + if i then break end
6.20 + local new = inp()
6.21 + if not new then break end
6.22 + do -- handle borders
6.23 + local endcurrent = strsub(current, -dellen+1)
6.24 + local border = endcurrent .. strsub(new, 1, dellen-1)
6.25 + if strlen(current) < dellen or strlen(new) < dellen or
6.26 + strfind(border, del, 1, 1) then
6.27 + -- move last part of `current' to new block
6.28 + current = strsub(current, 1, -dellen)
6.29 + new = endcurrent .. new
6.30 + end
6.31 + end
6.32 + out(current)
6.33 + current = new
6.34 + end
6.35 + out(strsub(current, 1, (i or 0) - 1))
6.36 + current = strsub(current, (e or strlen(current)) + 1)
6.37 + return (i ~= nil)
6.38 + end
6.39 +end
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/cgi-bin/cgilua/urlcode.lua Mon Feb 12 02:14:11 2007 +0100
7.3 @@ -0,0 +1,88 @@
7.4 +----------------------------------------------------------------------------
7.5 +-- $Id: urlcode.lua,v 1.3 2005/03/08 21:04:51 carregal Exp $
7.6 +----------------------------------------------------------------------------
7.7 +
7.8 +local next, tonumber, type = next, tonumber, type
7.9 +local string = string
7.10 +
7.11 +module "cgilua.urlcode"
7.12 +
7.13 +----------------------------------------------------------------------------
7.14 +-- Decode an URL-encoded string (see RFC 2396)
7.15 +----------------------------------------------------------------------------
7.16 +function unescape (str)
7.17 + str = string.gsub (str, "+", " ")
7.18 + str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end)
7.19 + str = string.gsub (str, "\r\n", "\n")
7.20 + return str
7.21 +end
7.22 +
7.23 +----------------------------------------------------------------------------
7.24 +-- URL-encode a string (see RFC 2396)
7.25 +----------------------------------------------------------------------------
7.26 +function escape (str)
7.27 + str = string.gsub (str, "\n", "\r\n")
7.28 + str = string.gsub (str, "([^%w ])",
7.29 + function (c) return string.format ("%%%02X", string.byte(c)) end)
7.30 + str = string.gsub (str, " ", "+")
7.31 + return str
7.32 +end
7.33 +
7.34 +----------------------------------------------------------------------------
7.35 +-- Insert a (name=value) pair into table [[args]]
7.36 +-- @param args Table to receive the result.
7.37 +-- @param name Key for the table.
7.38 +-- @param value Value for the key.
7.39 +-- Multi-valued names will be represented as tables with numerical indexes
7.40 +-- (in the order they came).
7.41 +----------------------------------------------------------------------------
7.42 +function insertfield (args, name, value)
7.43 + if not args[name] then
7.44 + args[name] = value
7.45 + else
7.46 + local t = type (args[name])
7.47 + if t == "string" then
7.48 + args[name] = {
7.49 + args[name],
7.50 + value,
7.51 + }
7.52 + elseif t == "table" then
7.53 + table.insert (args[name], value)
7.54 + else
7.55 + error ("CGILua fatal error (invalid args table)!")
7.56 + end
7.57 + end
7.58 +end
7.59 +
7.60 +----------------------------------------------------------------------------
7.61 +-- Parse url-encoded request data
7.62 +-- (the query part of the script URL or url-encoded post data)
7.63 +--
7.64 +-- Each decoded (name=value) pair is inserted into table [[args]]
7.65 +----------------------------------------------------------------------------
7.66 +function parsequery (query, args)
7.67 + if type(query) == "string" then
7.68 + local insertfield, unescape = insertfield, unescape
7.69 + string.gsub (query, "([^&=]+)=([^&=]*)&?",
7.70 + function (key, val)
7.71 + insertfield (args, unescape(key), unescape(val))
7.72 + end)
7.73 + end
7.74 +end
7.75 +
7.76 +----------------------------------------------------------------------------
7.77 +-- URL-encode the elements of a table creating a string to be used in a
7.78 +-- URL for passing data/parameters to another script
7.79 +----------------------------------------------------------------------------
7.80 +function encodetable (args)
7.81 + if args == nil or next(args) == nil then -- no args or empty args?
7.82 + return ""
7.83 + end
7.84 + local strp = ""
7.85 + for key,val in args do
7.86 + strp = strp.."&"..escape(key).."="..escape(val)
7.87 + end
7.88 + -- remove first &
7.89 + return string.sub(strp,2)
7.90 +end
7.91 +
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/cgi-bin/loona.lua Mon Feb 12 02:14:11 2007 +0100
8.3 @@ -0,0 +1,794 @@
8.4 +
8.5 +--
8.6 +-- loona - tiny CMS
8.7 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
8.8 +-- See copyright notice in COPYRIGHT
8.9 +--
8.10 +
8.11 +require "tek"
8.12 +require "tek.cgi"
8.13 +require "tek.cgi.request"
8.14 +require "tek.cgi.request.args"
8.15 +require "tek.cgi.header"
8.16 +require "tek.cgi.session"
8.17 +require "tek.posix"
8.18 +require "tek.web"
8.19 +require "tek.web.markup"
8.20 +require "tek.util"
8.21 +
8.22 +
8.23 +local boxed_G = {
8.24 + string = string, table = table,
8.25 + assert = assert, collectgarbage = collectgarbage, dofile = dofile,
8.26 + error = error, getfenv = getfenv, getmetatable = getmetatable,
8.27 + ipairs = ipairs, load = load, loadfile = loadfile, loadstring = loadstring,
8.28 + next = next, pairs = pairs, pcall = pcall, print = print,
8.29 + rawequal = rawequal, rawget = rawget, rawset = rawset, require = require,
8.30 + select = select, setfenv = setfenv, setmetatable = setmetatable,
8.31 + tonumber = tonumber, tostring = tostring, type = type, unpack = unpack,
8.32 + xpcall = xpcall
8.33 +}
8.34 +
8.35 +local tek, table, string, assert, unpack, ipairs, pairs, type, require =
8.36 + tek, table, string, assert, unpack, ipairs, pairs, type, require
8.37 +local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv
8.38 +local open, remove, rename, getenv, time =
8.39 + io.open, os.remove, os.rename, os.getenv, os.time
8.40 +
8.41 +local sectionfname, langs, locale
8.42 +
8.43 +
8.44 +module "loona"
8.45 +
8.46 +
8.47 +_VERSION = 2
8.48 +_REVISION = 0
8.49 +
8.50 +
8.51 +out = tek.web.out
8.52 +setheader = tek.web.setheader
8.53 +cgi = tek.cgi
8.54 +session = cgi.session
8.55 +request = cgi.request
8.56 +args = request.args
8.57 +posix = tek.posix
8.58 +encodeform = cgi.encodeform
8.59 +loadhtml = tek.web.include.load
8.60 +source = tek.source
8.61 +domarkup = tek.web.markup.main
8.62 +expire = tek.util.expire
8.63 +
8.64 +
8.65 +local function checkprofilename(c)
8.66 + assert(c:match("^%w+$") and c ~= "current", loc("INVALID_NAME"))
8.67 + return c
8.68 +end
8.69 +
8.70 +
8.71 +local function checksectionname(s)
8.72 + s = s or "main"
8.73 + assert(s:match("^[%w_]*%w+[%w_]*$"), loc("INVALID_NAME"))
8.74 + return s
8.75 +end
8.76 +
8.77 +
8.78 +local function deletedir(dst)
8.79 + for e in tek.util.readdir(dst) do
8.80 + local success, msg = remove(dst .. "/" .. e)
8.81 + assert(success, msg and "Error removing entry in profile" .. msg)
8.82 + end
8.83 + return remove(dst)
8.84 +end
8.85 +
8.86 +
8.87 +local function copyprofile(contentdir, lang, srcprofile, newprofile)
8.88 + local src = contentdir .. "/" .. srcprofile .. "_" .. lang
8.89 + assert(posix.stat(src, "mode") == "directory", "Not a directory : " .. src)
8.90 + local dst = contentdir .. "/" .. newprofile .. "_" .. lang
8.91 + local success, msg = posix.mkdir(dst)
8.92 + assert(success, msg and
8.93 + "Error creating profile directory " .. dst .. " : " .. msg)
8.94 + for e in tek.util.readdir(src) do
8.95 + local ext = e:match("^[^.].*%.([^.]*)$")
8.96 + if ext ~= "LOCK" then
8.97 + local f = src .. "/" .. e
8.98 + if posix.stat(f, "mode") == "file" then
8.99 + success, msg = tek.copyfile(f, dst .. "/" .. e)
8.100 + assert(success, msg and
8.101 + "Error copying file : " .. f .. " : " .. msg)
8.102 + end
8.103 + end
8.104 + end
8.105 +end
8.106 +
8.107 +
8.108 +local function publishprofile(contentdir, lang, profile)
8.109 + local oldpath = profile .. "_" .. lang
8.110 + local newpath = contentdir .. "/current_" .. lang
8.111 + local success, msg = posix.symlink(oldpath, newpath .. ".temp")
8.112 + assert(success, msg and
8.113 + "Cannot create symlink " .. newpath .. " : " .. msg)
8.114 + success, msg = rename(newpath .. ".temp", newpath)
8.115 + assert(success, msg and
8.116 + "Cannot overwrite symlink " .. newpath .. " : " .. msg)
8.117 +end
8.118 +
8.119 +
8.120 +local function lookup(tab, key, value)
8.121 + if tab then
8.122 + for i, v in ipairs(tab) do
8.123 + if v[key] == value then
8.124 + return i
8.125 + end
8.126 + end
8.127 + end
8.128 +end
8.129 +
8.130 +
8.131 +local function processsections(s)
8.132 + s = s or config.sections
8.133 + for _, e in ipairs(s) do
8.134 + if e.subs then
8.135 + processsections(e.subs)
8.136 + end
8.137 + e.notvalid = (not secure and e.secure) or
8.138 + (not authuser and e.secret) or nil
8.139 + e.notvisible = e.notvalid or not authuser and e.hidden or nil
8.140 + s[e.name] = e
8.141 + end
8.142 +end
8.143 +
8.144 +
8.145 +-- decompose section path into a stack of sections, returning only up to
8.146 +-- the last valid element in the path. additionally returns the table of
8.147 +-- the last section path element (or the default section)
8.148 +
8.149 +local function getsection(config, section, authuser, path, default)
8.150 + local tab = { { entries = config.sections, name = default } }
8.151 + local sections = config.sections
8.152 + local sectionpath
8.153 + (path or default):gsub("(%w+)/?", function(a)
8.154 + if sections then
8.155 + local i = lookup(sections, "name", a)
8.156 + if i and (authuser or not sections[i].secret) and
8.157 + (not sections[i].secure or secure) then
8.158 + sectionpath = sections[i]
8.159 + tab[#tab].name = a
8.160 + sections = sections[i].subs
8.161 + if sections then
8.162 + table.insert(tab, { entries = sections })
8.163 + end
8.164 + else
8.165 + sections = nil -- stop.
8.166 + end
8.167 + end
8.168 + end)
8.169 + if not section and not sectionpath then
8.170 + sectionpath = config.sections[lookup(config.sections, "name", default)]
8.171 + if sectionpath then
8.172 + table.insert(tab, { entries = sectionpath.subs })
8.173 + end
8.174 + end
8.175 + return tab, sectionpath
8.176 +end
8.177 +
8.178 +
8.179 +local function getpath(sections, delimiter)
8.180 + local t = { }
8.181 + for _, menu in ipairs(sections) do
8.182 + if menu.name then
8.183 + table.insert(t, menu.name)
8.184 + end
8.185 + end
8.186 + return table.concat(t, delimiter or "/")
8.187 +end
8.188 +
8.189 +
8.190 +-- descending into the sections table alongside the current path,
8.191 +-- return the filename to include as the sideborder, defaulting to
8.192 +-- its parent value, or the default specified
8.193 +
8.194 +local function getsidefile(sections, path, ext, default)
8.195 + local val
8.196 + local fname = ""
8.197 + for _, menu in ipairs(sections) do
8.198 + if lookup(menu.entries, "name", menu.name) then
8.199 + fname = fname .. menu.name
8.200 + local fn = fname .. ext
8.201 + if posix.stat(path .. "/" .. fn, "mode") == "file" then
8.202 + val = fn
8.203 + end
8.204 + fname = fname .. "_"
8.205 + end
8.206 + end
8.207 + return val or default
8.208 +end
8.209 +
8.210 +
8.211 +local function deletenode(dir, fname)
8.212 + local fullname = dir .. "/" .. fname
8.213 + local success, msg = remove(fullname)
8.214 + if success then
8.215 + local pat = "^" ..
8.216 + fname:gsub("%^%$%(%)%%%.%[%]%*%+%-%?", "%%%1") .. "%..*$"
8.217 + for e in tek.util.readdir(dir) do
8.218 + if e:match(pat) then
8.219 + remove(dir .. "/" .. e)
8.220 + end
8.221 + end
8.222 + end
8.223 + return success, msg
8.224 +end
8.225 +
8.226 +
8.227 +-- add element to path
8.228 +
8.229 +local function addtopath(tab, path, set)
8.230 + local stop
8.231 + path:gsub("(%w+)/?", function(a)
8.232 + if not stop and tab then
8.233 + local i = lookup(tab, "name", a)
8.234 + if i then
8.235 + if not tab[i].subs then
8.236 + tab[i].subs = {}
8.237 + end
8.238 + tab = tab[i].subs
8.239 + else
8.240 + table.insert(tab, set)
8.241 + stop = true
8.242 + end
8.243 + end
8.244 + end)
8.245 +end
8.246 +
8.247 +
8.248 +-- overwrite element in path
8.249 +
8.250 +local function overwritepath(tab, path, set)
8.251 + local stop, parent
8.252 + path:gsub("(%w+)/?", function(a)
8.253 + if not stop and tab then
8.254 + local i = lookup(tab, "name", a)
8.255 + if i then
8.256 + if tab[i].subs then
8.257 + parent = tab[i]
8.258 + tab = tab[i].subs
8.259 + else
8.260 + if not set then
8.261 + table.remove(tab, i)
8.262 + if #tab == 0 and parent then
8.263 + parent.subs = nil
8.264 + end
8.265 + else
8.266 + tab[i] = set
8.267 + end
8.268 + stop = true
8.269 + end
8.270 + end
8.271 + end
8.272 + end)
8.273 +end
8.274 +
8.275 +
8.276 +-------------------------------------------------------------------------------
8.277 +
8.278 +
8.279 +-- Return locale string
8.280 +
8.281 +function loc(s)
8.282 + if not locale then
8.283 + for _, l in ipairs(langs) do
8.284 + locale = source(config.localedir .. "/" .. l)
8.285 + if locale then
8.286 + break
8.287 + end
8.288 + end
8.289 + end
8.290 + return locale and locale[s] or 'nonlocalized message: "' .. s .. '"'
8.291 +end
8.292 +
8.293 +
8.294 +-- Find element in path
8.295 +
8.296 +function checkpath(tab, path)
8.297 + local res, idx
8.298 + path:gsub("(%w+)/?", function(a)
8.299 + if tab then
8.300 + local i = lookup(tab, "name", a)
8.301 + if i then
8.302 + res, idx = tab, i
8.303 + tab = tab[i].subs
8.304 + else
8.305 + res, idx = nil, nil
8.306 + end
8.307 + end
8.308 + end)
8.309 + return res, idx
8.310 +end
8.311 +
8.312 +
8.313 +-- Run a site function snippet, with full error recovery
8.314 +-- (also recovers from errors in error handling function)
8.315 +
8.316 +function dosnippet(config, func, errfunc, showdetail)
8.317 + local ret = { tek.catch(func) }
8.318 + if ret[1] == 0 or (errfunc and tek.catch(errfunc) == 0) then
8.319 + return unpack(ret)
8.320 + end
8.321 + out("<h2>Error</h2>")
8.322 + out("<h3>" .. cgi.encodeform(ret[2]) .. "</h3>")
8.323 + if showdetail or config.debug == true then
8.324 + if type(ret[3]) == "string" then
8.325 + out("<p>" .. cgi.encodeform(ret[3]) .. "</p>")
8.326 + end
8.327 + if ret[4] and config.debug == true then
8.328 + out("<pre>" .. cgi.encodeform(ret[4]) .. "</pre>")
8.329 + end
8.330 + end
8.331 +end
8.332 +
8.333 +
8.334 +function lockfile(newfile)
8.335 + return not session and true or
8.336 + posix.symlink(session.filename, newfile .. ".LOCK")
8.337 +end
8.338 +
8.339 +
8.340 +function unlockfile(dstfile)
8.341 + return not session and true or remove(dstfile .. ".LOCK")
8.342 +end
8.343 +
8.344 +
8.345 +function savenode(dir, fname, content)
8.346 + fname = dir .. "/" .. fname
8.347 + local f, msg = open(fname, "wb")
8.348 + assert(f, msg and
8.349 + "Could not open file " .. fname .. " for writing : " .. msg)
8.350 + f:write(content or "")
8.351 + f:close()
8.352 +end
8.353 +
8.354 +
8.355 +function include(fname, ...)
8.356 + assert(not fname:match("%W"), loc("INVALID_NAME") .. " : " .. fname)
8.357 + local fname2 = config.extdir .. "/" .. fname .. ".lua"
8.358 + local f, msg = open(fname2)
8.359 + assert(f, msg)
8.360 + local parsed, msg = loadhtml(f, "loona.out", fname2)
8.361 + assert(parsed, msg and "Syntax error : " .. msg)
8.362 + local fenv = {
8.363 + arg = arg,
8.364 + loona = {
8.365 + out = out,
8.366 + setheader = setheader,
8.367 + hidden = hidden,
8.368 + alink = alink,
8.369 + elink = elink,
8.370 + href = href,
8.371 + checkpath = checkpath,
8.372 + authuser = authuser,
8.373 + document = document,
8.374 + contentdir = contentdir,
8.375 + profile = profile,
8.376 + pubprofile = pubprofile,
8.377 + lang = lang,
8.378 + secure = secure,
8.379 + config = config,
8.380 + session = session,
8.381 + sectionpath = sectionpath,
8.382 + }
8.383 + }
8.384 + setmetatable(fenv, { __index = boxed_G }) -- boxed global environment
8.385 + setfenv(parsed, fenv)
8.386 + return parsed()
8.387 +end
8.388 +
8.389 +
8.390 +function href(section, ...)
8.391 + local target = cgi.document.Name
8.392 + target = section and target .. "/" .. section or target
8.393 + if session or args.profile and args.profile ~= pubprofile then
8.394 + return tek.web.gethref(target, "lang", "profile", "session",
8.395 + unpack(arg))
8.396 + end
8.397 + return tek.web.gethref(target, "lang", unpack(arg))
8.398 +end
8.399 +
8.400 +
8.401 +function link(section, text, ...) -- normal link
8.402 + return '<a href="' .. href(section, unpack(arg)) .. '">' ..
8.403 + (text or section) .. '</a>'
8.404 +end
8.405 +
8.406 +
8.407 +function alink(section, text, ...) -- active link
8.408 + return '<a class="active" href="' .. href(section, unpack(arg)) ..
8.409 + '">' .. (text or section) .. '</a>'
8.410 +end
8.411 +
8.412 +
8.413 +function elink(target, text) -- external link
8.414 + return '<a href="' .. target ..
8.415 + '" onclick="void(window.open(this.href, \'\', \'\')); return false;">'
8.416 + .. (text or target) .. '</a>'
8.417 +end
8.418 +
8.419 +
8.420 +function hidden(name, value)
8.421 + return not value and "" or
8.422 + '<input type="hidden" name="' .. name .. '" value="' .. value .. '" />'
8.423 +end
8.424 +
8.425 +
8.426 +function getprofiles(contentdir, lang)
8.427 + local t = { }
8.428 + for f in tek.util.readdir(contentdir) do
8.429 + if posix.lstat(contentdir .. "/" .. f, "mode") == "directory" then
8.430 + local e = f:match("^(%w+)_" .. lang .. "$")
8.431 + if e then
8.432 + t[e] = e
8.433 + end
8.434 + end
8.435 + end
8.436 + return t
8.437 +end
8.438 +
8.439 +
8.440 +-- Init
8.441 +
8.442 +local function init()
8.443 +
8.444 + -- get list of languages, in order of preference
8.445 +
8.446 + langs = { args.lang and args.lang:match("^%w+$") }
8.447 + if config.browserlang == true then
8.448 + local s = getenv("HTTP_ACCEPT_LANGUAGE")
8.449 + while s do
8.450 + local l, r = s:match("^([%w.=]+)[,;](.*)$")
8.451 + l = l or s
8.452 + s = r
8.453 + if l:match("^%w+$") then
8.454 + table.insert(langs, l)
8.455 + end
8.456 + end
8.457 + end
8.458 + table.insert(langs, config.deflang)
8.459 +
8.460 + -- get list of possible profiles
8.461 +
8.462 + local profiles = { }
8.463 + for e in tek.util.readdir(config.contentdir) do
8.464 + profiles[e] = e
8.465 + end
8.466 +
8.467 + -- get pubprofile
8.468 +
8.469 + for _, lang in ipairs(langs) do
8.470 + local p = posix.readlink(config.contentdir .. "/current_" .. lang)
8.471 + p = p and p:match("^(%w+)_" .. lang .. "$")
8.472 + if p then
8.473 + pubprofile = p
8.474 + break
8.475 + end
8.476 + end
8.477 +
8.478 + -- get profile
8.479 +
8.480 + local checkprofile = authuser and args.profile or pubprofile or "default"
8.481 + for _, l in ipairs(langs) do
8.482 + if profiles[checkprofile .. "_" .. l] then
8.483 + profile = checkprofile
8.484 + lang = l
8.485 + break
8.486 + end
8.487 + end
8.488 +
8.489 + assert(lang, "No content for the default language")
8.490 +
8.491 + -- write back language and profile into args
8.492 +
8.493 + args.lang = lang ~= config.deflang and lang or nil
8.494 + args.profile = profile
8.495 +
8.496 + -- determine content directory pathname and section filename
8.497 +
8.498 + contentdir = config.contentdir .. "/" .. profile .. "_" .. lang
8.499 + sectionfname = contentdir .. "/.sections"
8.500 +
8.501 + -- load sections
8.502 +
8.503 + config.sections = source(sectionfname)
8.504 +
8.505 + -- index sections, determine visibility
8.506 +
8.507 + processsections(config.sections)
8.508 +
8.509 + -- decompose section path, produce a stack of sections
8.510 +
8.511 + submenus, section = getsection(config, section, authuser,
8.512 + cgi.document.VirtualPath or "", not authuser and config.defname)
8.513 +
8.514 + -- handle redirects if not logged on
8.515 +
8.516 + if not authuser and section and section.redirect then
8.517 + submenus, section = getsection(config, section, authuser,
8.518 + section.redirect, not authuser and config.defname)
8.519 + end
8.520 +
8.521 + -- section path and document name (refined)
8.522 +
8.523 + sectionpath = getpath(submenus)
8.524 +
8.525 +end
8.526 +
8.527 +
8.528 +-- Handle state modifications (creating/saving/deleting, profile management)
8.529 +
8.530 +local function handlestate()
8.531 + if args.editkey == "main" then
8.532 + --
8.533 + -- in main editable section:
8.534 + --
8.535 + local reload = false
8.536 +
8.537 + if args.actioncreate then
8.538 + --
8.539 + -- create new node
8.540 + --
8.541 + local editname = args.editname:lower()
8.542 + assert(not editname:match("%W"), loc("INVALID_NAME"))
8.543 + if not (section and lookup(section.subs or section, "name", editname)) then
8.544 + local newpath = (sectionpath and (sectionpath .. "/")) .. editname
8.545 + addtopath(config.sections, newpath, { name = editname,
8.546 + label = args.editlabel ~= "" and args.editlabel or nil,
8.547 + title = args.edittitle ~= "" and args.edittitle or nil,
8.548 + creator = authuser,
8.549 + creationdate = time(),
8.550 + })
8.551 + reload = true
8.552 + end
8.553 + elseif args.actionsave then
8.554 + --
8.555 + -- save node
8.556 + --
8.557 + section.revisiondate = time()
8.558 + section.revisioner = authuser
8.559 + reload = true
8.560 + elseif args.actiondelete then
8.561 + --
8.562 + -- delete node
8.563 + --
8.564 + if not args.actionconfirm then
8.565 + useralert = { text = loc("ALERT_DELETE_NODE"), confirm =
8.566 + '<input type="submit" name="actiondelete" value="' ..
8.567 + loc("DELETE") .. '" /> ' ..
8.568 + hidden("actionconfirm", "true") }
8.569 + else
8.570 + deletenode(contentdir, sectionpath:gsub("/", "_"))
8.571 + overwritepath(config.sections, sectionpath, nil)
8.572 + reload = true
8.573 + end
8.574 + elseif args.actionsaveprops then
8.575 + --
8.576 + -- save properties
8.577 + --
8.578 + section.hidden = args.editvisibility and true
8.579 + section.secret = args.editsecrecy and true
8.580 + section.secure = args.editsecure and true
8.581 + section.label = args.editlabel ~= "" and args.editlabel or nil
8.582 + section.title = args.edittitle ~= "" and args.edittitle or nil
8.583 + section.redirect = args.editredirect ~= "" and args.editredirect or nil
8.584 + reload = true
8.585 + elseif args.actionup then
8.586 + --
8.587 + -- move node up
8.588 + --
8.589 + local t, i = checkpath(config.sections, sectionpath)
8.590 + if t and i > 1 then
8.591 + local item = table.remove(t, i)
8.592 + table.insert(t, i - 1, item)
8.593 + reload = true
8.594 + end
8.595 + elseif args.actiondown then
8.596 + --
8.597 + -- move node down
8.598 + --
8.599 + local t, i = checkpath(config.sections, sectionpath)
8.600 + if t and i < #t then
8.601 + local item = table.remove(t, i)
8.602 + table.insert(t, i + 1, item)
8.603 + reload = true
8.604 + end
8.605 + elseif args.actioncreateprofile and args.createprofile then
8.606 + --
8.607 + -- create profile
8.608 + --
8.609 + local c = checkprofilename(args.createprofile:lower())
8.610 + if c == profile then
8.611 + useralert = { text = loc("ALERT_CANNOT_COPY_PROFILE_TO_SELF") }
8.612 + else
8.613 + local profiles = getprofiles(config.contentdir, lang)
8.614 + if profiles[c] and not args.actionconfirm then
8.615 + useralert = { text = c == pubprofile and
8.616 + loc("ALERT_OVERWRITE_PUBLISHED_PROFILE") or
8.617 + loc("ALERT_OVERWRITE_EXISTING_PROFILE"),
8.618 + confirm =
8.619 + '<input type="submit" name="actioncreateprofile" value="' .. loc("OVERWRITE") .. '" /> ' ..
8.620 + hidden("actionconfirm", "true") .. hidden("createprofile", c) }
8.621 + else
8.622 + if profiles[c] then
8.623 + deletedir(config.contentdir .. "/" .. c .. "_" .. lang)
8.624 + end
8.625 + copyprofile(config.contentdir, lang, profile, c)
8.626 + end
8.627 + end
8.628 + elseif args.actiondeleteprofile and args.deleteprofile then
8.629 + --
8.630 + -- delete profile
8.631 + --
8.632 + local c = checkprofilename(args.deleteprofile:lower())
8.633 + assert(c ~= pubprofile, loc("CANNOT_DELETE_PUBLISHED_PROFILE"))
8.634 + if args.actionconfirm then
8.635 + deletedir(config.contentdir .. "/" .. c .. "_" .. lang)
8.636 + profile = nil
8.637 + args.profile = nil
8.638 + init() -- for getting new sectionfname etc.
8.639 + reload = true
8.640 + else
8.641 + useralert = { text = loc("ALERT_DELETE_PROFILE"), confirm =
8.642 + '<input type="submit" name="actiondeleteprofile" value="' .. loc("DELETE") .. '" /> ' ..
8.643 + hidden("actionconfirm", "true") ..
8.644 + hidden("deleteprofile", c) }
8.645 + end
8.646 + elseif args.actionchangeprofile and args.changeprofile then
8.647 + --
8.648 + -- change profile
8.649 + --
8.650 + local c = checkprofilename(args.changeprofile:lower())
8.651 + profile = c
8.652 + args.profile = c
8.653 + reload = true
8.654 + elseif args.actionpublishprofile and args.publishprofile then
8.655 + --
8.656 + -- publish profile
8.657 + --
8.658 + local c = checkprofilename(args.publishprofile:lower())
8.659 + if c ~= _publicprofile then
8.660 + if args.actionconfirm then
8.661 + publishprofile(config.contentdir, lang, c)
8.662 + reload = true
8.663 + else
8.664 + useralert = { text = loc("ALERT_PUBLISH_PROFILE"), confirm =
8.665 + '<input type="submit" name="actionpublishprofile" value="' .. loc("PUBLISH") .. '" /> ' ..
8.666 + hidden("actionconfirm", "true") ..
8.667 + hidden("publishprofile", c) }
8.668 + end
8.669 + end
8.670 + end
8.671 +
8.672 + if reload then
8.673 + --
8.674 + -- write sections, reload
8.675 + --
8.676 + local tempname = sectionfname .. ".temp"
8.677 + local f, msg = open(tempname, "wb")
8.678 + assert(f, msg and "Error opening section file for writing : " .. msg)
8.679 + tek.dump(config.sections, function(...)
8.680 + f:write(unpack(arg))
8.681 + end)
8.682 + f:close()
8.683 + local success, msg = rename(tempname, sectionfname)
8.684 + assert(success, msg and "Error renaming section file : " .. msg)
8.685 + init()
8.686 + end
8.687 +
8.688 + elseif args.editkey and checksectionname(args.editkey) then
8.689 + if args.actiondelete then
8.690 + --
8.691 + -- delete node in secondary editable section:
8.692 + --
8.693 + deletenode(contentdir, sectionpath:gsub("/", "_") .. "." .. args.editkey)
8.694 + end
8.695 + end
8.696 +end
8.697 +
8.698 +
8.699 +-- load configuration
8.700 +
8.701 +config = source("../etc/config.lua") or { }
8.702 +config.title = config.title or "Loona CMS"
8.703 +config.localedir = posix.abspath(config.localedir or "../locale")
8.704 +config.contentdir = posix.abspath(config.contentdir or "../content")
8.705 +config.sessiondir = posix.abspath(config.sessiondir or "../var/sessions")
8.706 +config.extdir = posix.abspath(config.extdir or "../extensions")
8.707 +config.passwdfile = posix.abspath(config.passwdfile or "../etc/passwd.lua")
8.708 +config.sessionmaxage = config.sessionmaxage or 600
8.709 +config.defname = config.defname or "home"
8.710 +config.deflang = config.deflang or "en"
8.711 +config.secureport = config.secureport or 443
8.712 +
8.713 +-- manage login and establish session
8.714 +
8.715 +session.init(config.sessiondir, args.session, config.sessionmaxage)
8.716 +if args.login then
8.717 + if args.login == "false" then
8.718 + session.delete()
8.719 + session = nil
8.720 + elseif args.password then
8.721 + local pwddb = source(config.passwdfile)
8.722 + local pwdentry = pwddb[args.login]
8.723 + if pwdentry and pwdentry.password == args.password then
8.724 + session.data.authuser = pwdentry.username
8.725 + session.data.id = session.id
8.726 + end
8.727 + end
8.728 +end
8.729 +
8.730 +if not (session and session.data.authuser) then
8.731 + session = nil
8.732 + args.session = nil
8.733 +end
8.734 +
8.735 +authuser = session and session.data.authuser
8.736 +secure = cgi.request.Port == config.secureport
8.737 +
8.738 +
8.739 +-- get lang, locale, profile, section
8.740 +init()
8.741 +
8.742 +-- handle state modifications
8.743 +if authuser then
8.744 + handlestate()
8.745 +end
8.746 +
8.747 +-- current document
8.748 +document = cgi.document.Name
8.749 +document = sectionpath and document .. "/" .. sectionpath
8.750 +
8.751 +-- get content filename from section path
8.752 +local fname = sectionpath:gsub("/", "_")
8.753 +
8.754 +-- add links for creating new nodes
8.755 +if authuser then
8.756 + for _, s in ipairs(submenus) do
8.757 + table.insert(s.entries, { name = "new", label = "[" .. loc("NEW") .. "]", action = "actionnew=true" })
8.758 + end
8.759 + if submenus[#submenus].name then
8.760 + table.insert(submenus, { name = "new", entries = { [1] = { name = "new", label = "[" .. loc("NEW") .. "]", action = "actionnew=true" }}})
8.761 + end
8.762 +end
8.763 +
8.764 +-- create section function
8.765 +
8.766 +local func, msg = loadhtml(open("loona/editable.lua"),
8.767 + "tek.web.out", "loona/editable.lua")
8.768 +
8.769 +assert(func, msg and "Syntax error : " .. msg)
8.770 +
8.771 +local editable = func()
8.772 +
8.773 +function body(name)
8.774 + name = checksectionname(name)
8.775 + if name == "main" then
8.776 + dosnippet(config, editable("main", contentdir, fname, fname))
8.777 + else
8.778 + local ext = "." .. name
8.779 + dosnippet(config, editable(name, contentdir,
8.780 + getsidefile(submenus, contentdir, ext), fname .. ext))
8.781 + end
8.782 +end
8.783 +
8.784 +-- write back session state
8.785 +
8.786 +if session then
8.787 + session.save()
8.788 +end
8.789 +
8.790 +
8.791 +-- do
8.792 +-- local f = open("/tmp/foo", "wb")
8.793 +-- tek.dump(config.sections, function(s)
8.794 +-- f:write(s)
8.795 +-- end)
8.796 +-- end
8.797 +
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/cgi-bin/loona/editable.lua Mon Feb 12 02:14:11 2007 +0100
9.3 @@ -0,0 +1,391 @@
9.4 +<%
9.5 +
9.6 +local function loadcontent(contentdir, fname)
9.7 + local c
9.8 + if fname then
9.9 + local f = io.open(contentdir .. "/" .. fname)
9.10 + if f then
9.11 + c = f:read("*a")
9.12 + f:close()
9.13 + end
9.14 + end
9.15 + return c or ""
9.16 +end
9.17 +
9.18 +
9.19 +local function loadmarkup(contentdir, fname)
9.20 + if fname then
9.21 + local htmlfname = contentdir .. "/" .. fname .. ".html"
9.22 + if loona.config.cachehtml == true then
9.23 + local f = io.open(htmlfname)
9.24 + if f then
9.25 + c = f:read("*a")
9.26 + f:close()
9.27 + if c then
9.28 + return c
9.29 + end
9.30 + end
9.31 + end
9.32 + local c, dynamic
9.33 + c = loadcontent(contentdir, fname)
9.34 + c, dynamic = loona.domarkup(c)
9.35 + if not dynamic and loona.config.cachehtml == true then
9.36 + f = io.open(htmlfname, "wb")
9.37 + if f then
9.38 + f:write(c)
9.39 + f:close()
9.40 + end
9.41 + end
9.42 + return c
9.43 + end
9.44 + return ""
9.45 +end
9.46 +
9.47 +
9.48 +do
9.49 + return function(editkey, contentdir, fname, savename)
9.50 + local function hiddenvars()%>
9.51 + <div>
9.52 + <%=loona.hidden("lang", loona.lang)%>
9.53 + <%=loona.hidden("profile", loona.profile)%>
9.54 + <%=loona.hidden("session", loona.session.id)%>
9.55 + <%=loona.hidden("editkey", editkey)%>
9.56 + </div>
9.57 + <%end
9.58 +
9.59 + local functions = { }
9.60 + local edit, show, hidden, extramsg, changed
9.61 +
9.62 + if loona.authuser then
9.63 + local lockfname = fname and (contentdir .. "/" .. fname)
9.64 +
9.65 + if loona.useralert and editkey == "main" then
9.66 + --
9.67 + -- display user alert/request/confirmation
9.68 + --
9.69 + hidden = true
9.70 + table.insert(functions, function()%>
9.71 + <h3><%=loona.useralert.text%></h3>
9.72 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.73 + <%hiddenvars()%>
9.74 + <div>
9.75 + <%=(loona.useralert.confirm or "")%>
9.76 + <input type="submit" name="actioncancel" value="<%=loona.loc("CANCEL")%>" />
9.77 + </div>
9.78 + </form>
9.79 + <%end)
9.80 +
9.81 + elseif loona.args.actionnew and editkey == "main" then
9.82 + --
9.83 + -- form for creating a new node
9.84 + --
9.85 + hidden = true
9.86 + table.insert(functions, function()%>
9.87 + <p><%=loona.loc("CREATE_NEW_SECTION_UNDER")%> /<%=loona.sectionpath%></p>
9.88 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.89 + <%hiddenvars()%>
9.90 + <table>
9.91 + <tr>
9.92 + <td align="right"><%=loona.loc("PATHNAME")%> :</td>
9.93 + <td><input size="30" maxlength="30" name="editname"/></td>
9.94 + </tr>
9.95 + <tr>
9.96 + <td align="right"><%=loona.loc("MENULABEL")%> :</td>
9.97 + <td><input size="30" maxlength="50" name="editlabel"/></td>
9.98 + </tr>
9.99 + <tr>
9.100 + <td align="right"><%=loona.loc("WINDOWTITLE")%> :</td>
9.101 + <td><input size="30" maxlength="50" name="edittitle"/></td>
9.102 + </tr>
9.103 + </table>
9.104 + <input type="submit" name="actioncreate" value="<%=loona.loc("CREATE")%>" />
9.105 + </form>
9.106 + <hr />
9.107 + <%end)
9.108 +
9.109 + elseif loona.args.actioneditprops and editkey == "main" then
9.110 + hidden = true
9.111 + table.insert(functions, function()%>
9.112 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.113 + <%hiddenvars()%>
9.114 + <table>
9.115 + <tr>
9.116 + <td align="right"><%=loona.loc("CREATOR")%> :</td>
9.117 + <td><%=((loona.section.creator and loona.section.creator) or "")%></td>
9.118 + </tr>
9.119 + <tr>
9.120 + <td align="right"><%=loona.loc("CREATIONDATE")%> :</td>
9.121 + <td><%=((loona.section.creationdate and os.date("%d-%b-%Y %T", loona.section.creationdate)) or "")%></td>
9.122 + </tr>
9.123 + <tr>
9.124 + <td align="right"><%=loona.loc("LASTREVISIONER")%> :</td>
9.125 + <td><%=((loona.section.revisioner and loona.section.revisioner) or "")%></td>
9.126 + </tr>
9.127 + <tr>
9.128 + <td align="right"><%=loona.loc("LASTREVISIONDATE")%> :</td>
9.129 + <td><%=((loona.section.revisiondate and os.date("%d-%b-%Y %T", loona.section.revisiondate)) or "")%></td>
9.130 + </tr>
9.131 + <tr>
9.132 + <td align="right"><%=loona.loc("MENULABEL")%> :</td>
9.133 + <td><input size="30" maxlength="50" name="editlabel" value="<%=(loona.section.label or "")%>" /></td>
9.134 + </tr>
9.135 + <tr>
9.136 + <td align="right"><%=loona.loc("WINDOWTITLE")%> :</td>
9.137 + <td><input size="30" maxlength="50" name="edittitle" value="<%=(loona.section.title or "")%>" /></td>
9.138 + </tr>
9.139 + <tr>
9.140 + <td align="right"><%=loona.loc("INVISIBLE")%> :</td>
9.141 + <td>
9.142 + <%if loona.section.hidden then%>
9.143 + <input type="checkbox" checked name="editvisibility" />
9.144 + <%else%>
9.145 + <input type="checkbox" name="editvisibility" />
9.146 + <%end%>
9.147 + </td>
9.148 + </tr>
9.149 + <tr>
9.150 + <td align="right"><%=loona.loc("SECRET")%> :</td>
9.151 + <td>
9.152 + <%if loona.section.secret then%>
9.153 + <input type="checkbox" checked name="editsecrecy" />
9.154 + <%else%>
9.155 + <input type="checkbox" name="editsecrecy" />
9.156 + <%end%>
9.157 + </td>
9.158 + </tr>
9.159 + <tr>
9.160 + <td align="right"><%=loona.loc("SECURE_CONNECTION")%> :</td>
9.161 + <td>
9.162 + <%if loona.section.secure then%>
9.163 + <input type="checkbox" checked name="editsecure" />
9.164 + <%else%>
9.165 + <input type="checkbox" name="editsecure" />
9.166 + <%end%>
9.167 + </td>
9.168 + </tr>
9.169 + <tr>
9.170 + <td align="right"><%=loona.loc("REDIRECT")%> :</td>
9.171 + <td><input size="30" maxlength="50" name="editredirect" value="<%=(loona.section.redirect or "")%>" /></td>
9.172 + </tr>
9.173 + </table>
9.174 + <div>
9.175 + <input type="submit" name="actionsaveprops" value="<%=loona.loc("SAVE")%>" />
9.176 + <input type="submit" name="actioncancel" value="<%=loona.loc("CANCEL")%>" />
9.177 + </div>
9.178 + </form>
9.179 + <%end)
9.180 + elseif (loona.args.actioneditprofiles or
9.181 + loona.args.actioncreateprofile or
9.182 + loona.args.actionchangeprofile or
9.183 + loona.args.actionpublishprofile) and editkey == "main" then
9.184 + hidden = true
9.185 + local profiles = { }
9.186 + for p in pairs(loona.getprofiles(loona.config.contentdir, loona.lang)) do
9.187 + table.insert(profiles, p)
9.188 + end
9.189 + table.sort(profiles)
9.190 + table.insert(functions, function()%>
9.191 + <table>
9.192 + <tr>
9.193 + <td>
9.194 + <%=loona.loc("CHANGEPROFILE")%> :
9.195 + </td>
9.196 + <td>
9.197 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.198 + <%hiddenvars()%>
9.199 + <select name="changeprofile" size="1">
9.200 + <%for _, val in ipairs(profiles) do%>
9.201 + <%if val == loona.profile then%>
9.202 + <option selected>
9.203 + <%else%>
9.204 + <option>
9.205 + <%end%>
9.206 + <%=val%>
9.207 + </option>
9.208 + <%end%>
9.209 + </select>
9.210 + <input type="submit" name="actionchangeprofile" value="<%=loona.loc("CHANGE")%>" />
9.211 + </form>
9.212 + </td>
9.213 + </tr>
9.214 + <tr>
9.215 + <td>
9.216 + <%=loona.loc("CREATEPROFILE")%> :
9.217 + </td>
9.218 + <td>
9.219 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.220 + <%hiddenvars()%>
9.221 + <input size="20" maxlength="20" name="createprofile" value="" />
9.222 + <input type="submit" name="actioncreateprofile" value="<%=loona.loc("CREATE")%>" />
9.223 + </form>
9.224 + </td>
9.225 + </tr>
9.226 + <%if loona.profile ~= loona.pubprofile then%>
9.227 + <tr>
9.228 + <td>
9.229 + <%=loona.loc("DELETEPROFILE")%> :
9.230 + </td>
9.231 + <td>
9.232 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.233 + <%hiddenvars()%>
9.234 + <%=loona.hidden("deleteprofile", loona.profile)%>
9.235 + <input type="submit" name="actiondeleteprofile" value="<%=loona.loc("DELETE")%>" />
9.236 + </form>
9.237 + </td>
9.238 + </tr>
9.239 + <tr>
9.240 + <td>
9.241 + <%=loona.loc("PUBLISHPROFILE")%> :
9.242 + </td>
9.243 + <td>
9.244 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
9.245 + <%hiddenvars()%>
9.246 + <%=loona.hidden("publishprofile", loona.profile)%>
9.247 + <input type="submit" name="actionpublishprofile" value="<%=loona.loc("PUBLISH")%>" />
9.248 + </form>
9.249 + </td>
9.250 + </tr>
9.251 + <%end%>
9.252 + </table>
9.253 + <%end)
9.254 + elseif loona.args.actionedit and editkey == loona.args.editkey then
9.255 + if not loona.section.redirect then
9.256 + edit = loadcontent(contentdir, fname):gsub("\194\160", " ")
9.257 + changed = loona.section and (loona.section.revisiondate or loona.section.creationdate)
9.258 + end
9.259 + elseif loona.args.actionpreview and editkey == loona.args.editkey then
9.260 + edit = loona.args.editform
9.261 + show = loona.domarkup(edit:gsub(" ", "\194\160"))
9.262 + elseif loona.args.actionsave and editkey == loona.args.editkey then
9.263 + local c = loona.args.editform
9.264 + if lockfname then
9.265 + loona.expire(contentdir, "[^.]%S+.LOCK")
9.266 + if loona.lockfile(lockfname) then
9.267 + -- lock was expired, aquired a new one
9.268 + extramsg = loona.loc("SECTION_COULD_HAVE_CHANGED")
9.269 + edit = c
9.270 + else
9.271 + local tab = tek.source(lockfname .. ".LOCK")
9.272 + if tab and tab.id == loona.session.id then
9.273 + -- lock already held and is mine - try to save:
9.274 + local savec = c:gsub(" ", "\194\160")
9.275 + os.remove(contentdir .. "/" .. savename .. ".html")
9.276 + local res = loona.savenode(contentdir, savename, savec)
9.277 + -- TODO: error handling
9.278 + loona.unlockfile(lockfname)
9.279 + show = loona.domarkup(savec)
9.280 + changed = os.time()
9.281 + else
9.282 + -- lock was expired and someone else has it now
9.283 + extramsg = loona.loc("SECTION_IN_USE")
9.284 + edit = c
9.285 + end
9.286 + end
9.287 + else
9.288 + -- new sidefile
9.289 + local savec = c:gsub(" ", "\194\160")
9.290 + loona.savenode(contentdir, savename, savec)
9.291 + -- TODO: error handling
9.292 + show = loona.domarkup(savec)
9.293 + end
9.294 + elseif loona.args.actioncancel and editkey == loona.args.editkey then
9.295 + if lockfname then
9.296 + loona.unlockfile(lockfname) -- remove lock
9.297 + end
9.298 + end
9.299 +
9.300 + if editkey == "main" and loona.section and loona.section.redirect then
9.301 + table.insert(functions, function()%>
9.302 + <h2><%=loona.loc("SECTION_IS_REDIRECT")%></h2>
9.303 + <%=loona.alink(loona.section.redirect)%>
9.304 + <hr />
9.305 + <%end)
9.306 + end
9.307 +
9.308 + end
9.309 +
9.310 + if edit then
9.311 + loona.expire(contentdir, "[^.]%S+.LOCK")
9.312 + if fname and not loona.lockfile(contentdir .. "/" .. fname) then
9.313 + local tab = tek.source(contentdir .. "/" .. fname .. ".LOCK")
9.314 + if tab and tab.id ~= loona.session.id then
9.315 + extramsg = loona.loc("SECTION_IN_USE")
9.316 + end
9.317 + -- else already owner
9.318 + end
9.319 + table.insert(functions, function()%>
9.320 + <%if extramsg then%>
9.321 + <h2><span class="warning"><%=extramsg%></span></h2>
9.322 + <%end%>
9.323 + <form action="<%=loona.document%>#preview" method="post" accept-charset="utf-8">
9.324 + <%hiddenvars()%>
9.325 + <div>
9.326 + <textarea cols="100" rows="20" name="editform"><%=loona.encodeform(edit)%></textarea>
9.327 + </div>
9.328 + <div>
9.329 + <input type="submit" name="actionsave" value="<%=loona.loc("SAVE")%>" />
9.330 + <input type="submit" name="actionpreview" value="<%=loona.loc("PREVIEW")%>" />
9.331 + <input type="submit" name="actioncancel" value="<%=loona.loc("CANCEL")%>" />
9.332 + </div>
9.333 + </form>
9.334 + <%end)
9.335 + end
9.336 +
9.337 + if not hidden then
9.338 + table.insert(functions, function()
9.339 + loona.dosnippet(loona.config, function()
9.340 + if not show then
9.341 + show = loadmarkup(contentdir, fname)
9.342 + changed = loona.section and (loona.section.revisiondate or loona.section.creationdate)
9.343 + end
9.344 + local parsed, msg = loona.loadhtml(show, "loona.out", "<parsed html>")
9.345 + assert(parsed, msg and "Syntax error : " .. msg)
9.346 + parsed()
9.347 + end, nil, loona.config.debug)
9.348 + end)
9.349 + end
9.350 +
9.351 + if loona.authuser then
9.352 + table.insert(functions, function()%>
9.353 + <hr />
9.354 + <div>
9.355 + <%if editkey == "main" then%>
9.356 + <a name="preview"></a>
9.357 + <%=loona.authuser%> :
9.358 + <%=loona.alink(loona.sectionpath, "[" .. loona.loc("PROFILE") .. "]", "actioneditprofiles=true", "editkey=" .. editkey)%> :
9.359 + <%=loona.profile%> (<%=loona.lang%>)
9.360 + <%if loona.pubprofile == loona.profile then%>
9.361 + <span class="warning">[<%=loona.loc("PUBLIC")%>]</span>
9.362 + <%end%>
9.363 + - <%=loona.sectionpath%>
9.364 + <%end%>
9.365 + <%if loona.section then%>
9.366 + <%=loona.alink(loona.sectionpath, "[" .. loona.loc("EDIT") .. "]", "actionedit=true", "editkey=" .. editkey)%>
9.367 + <%if editkey == "main" then%>
9.368 + - <%=loona.alink(loona.sectionpath, "[" .. loona.loc("PROPERTIES") .. "]", "actioneditprops=true", "editkey=" .. editkey)%>
9.369 + <%end%>
9.370 + <%if (editkey == "main" and not loona.section.subs) or (editkey ~= "main" and fname == savename) then%>
9.371 + - <%=loona.alink(loona.sectionpath, "[" .. loona.loc("DELETE") .. "]", "actiondelete=true", "editkey=" .. editkey)%>
9.372 + <%end%>
9.373 + <%if editkey == "main" then%>
9.374 + - <%=loona.alink(loona.sectionpath, "[" .. loona.loc("MOVEUP") .. "]", "actionup=true", "editkey=" .. editkey)%>
9.375 + - <%=loona.alink(loona.sectionpath, "[" .. loona.loc("MOVEDOWN") .. "]", "actiondown=true", "editkey=" .. editkey)%>
9.376 + <%end%>
9.377 + <%if changed and editkey == "main" then%>
9.378 + - <%=loona.loc("CHANGED")%>: <%=os.date("%d-%b-%Y %T", changed)%>
9.379 + <%end%>
9.380 + <%end%>
9.381 + </div>
9.382 + <%end)
9.383 + end
9.384 +
9.385 + return function()
9.386 + for _, f in ipairs(functions) do
9.387 + f()
9.388 + end
9.389 + end
9.390 +
9.391 + end
9.392 +
9.393 +end
9.394 +%>
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/cgi-bin/tek.lua Mon Feb 12 02:14:11 2007 +0100
10.3 @@ -0,0 +1,293 @@
10.4 +--
10.5 +-- tek - TEK library
10.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
10.7 +-- See copyright notice in COPYRIGHT
10.8 +--
10.9 +
10.10 +require "tek.posix"
10.11 +
10.12 +
10.13 +local type, unpack, table, pairs, ipairs = type, unpack, table, pairs, ipairs
10.14 +local error, xpcall, traceback = error, xpcall, debug.traceback
10.15 +local open, floor, char, rep = io.open, math.floor, string.char, string.rep
10.16 +local loadstring, type, tostring = loadstring, type, tostring
10.17 +
10.18 +
10.19 +module "tek"
10.20 +
10.21 +_VERSION = 2
10.22 +_REVISION = 0
10.23 +
10.24 +
10.25 +-- Throw error
10.26 +
10.27 +function throw(code, text, detail, trace)
10.28 + error { code = code, text = text, detail = detail,
10.29 + trace = trace or traceback("", 2) }
10.30 +end
10.31 +
10.32 +
10.33 +-- Catch errors when executing a function; returns an error code (0 okay,
10.34 +-- <0 execution error, >0 user error), followed by a string representing
10.35 +-- the error, followed by the regular results from the function
10.36 +
10.37 +function catch(func, ...)
10.38 +
10.39 + local function callwithargs()
10.40 + func(args)
10.41 + end
10.42 +
10.43 + -- local error handler preserves traceback + errmessage:
10.44 + local function err(obj)
10.45 + if type(obj) == "table" then
10.46 + obj.trace = obj.trace or traceback("", 2)
10.47 + else
10.48 + obj = { text = obj, trace = traceback("", 2) }
10.49 + end
10.50 + return obj
10.51 + end
10.52 +
10.53 + local res = { xpcall(callwithargs, err) }
10.54 +
10.55 + if table.remove(res, 1) then
10.56 + return 0, "ok", unpack(res)
10.57 + end
10.58 +
10.59 + -- custom error
10.60 + return res[1].code, res[1].text, res[1].detail, res[1].trace
10.61 +
10.62 +end
10.63 +
10.64 +
10.65 +-- Source a file into a table (in empty environment, by default)
10.66 +
10.67 +function source(filename, env)
10.68 + local f, msg = open(filename)
10.69 + if f then
10.70 + local chunk = f:read("*a")
10.71 + f:close()
10.72 + if chunk then
10.73 + chunk, msg = loadstring("do return {\n" .. chunk .. "\n} end")
10.74 + if chunk then
10.75 + if env then
10.76 + setfenv(chunk, env)
10.77 + end
10.78 + return chunk()
10.79 + end
10.80 + end
10.81 + end
10.82 + return nil, msg
10.83 +end
10.84 +
10.85 +
10.86 +-- Recursive serialize -
10.87 +-- note that cyclic dependencies are silently dropped
10.88 +
10.89 +local function serialize(tab, sorted, indent, outfunc, saved)
10.90 + saved[tab] = tab
10.91 + local is = rep("\t", indent)
10.92 + local set = { }
10.93 + for key, val in pairs(tab) do
10.94 + local t = type(val)
10.95 + if t ~= "table" or not saved[val] then
10.96 + table.insert(set, { cmp = tostring(key):lower(), key = key, val = val })
10.97 + end
10.98 + end
10.99 + if sorted then
10.100 + table.sort(set, function(a, b)
10.101 + if type(a.key) == "number" and type(b.key) == "number" then
10.102 + return a.key < b.key
10.103 + end
10.104 + return a.cmp < b.cmp
10.105 + end)
10.106 + end
10.107 + for _, e in ipairs(set) do
10.108 + local key, val = e.key, e.val
10.109 + local t = type(val)
10.110 +
10.111 + if not saved[val] then
10.112 + outfunc(is)
10.113 + if type(key) == "number" then
10.114 + outfunc('[' .. key .. '] = ')
10.115 + else
10.116 + if key:match("[^%a_]") then
10.117 + outfunc('["' .. key .. '"] = ')
10.118 + else
10.119 + outfunc(key .. ' = ')
10.120 + end
10.121 + end
10.122 + if t == "table" then
10.123 + outfunc('{\n')
10.124 + serialize(val, sorted, indent + 1, outfunc, saved)
10.125 + outfunc(is .. '},\n')
10.126 + elseif t == "number" then
10.127 + outfunc(tostring(val) .. ',\n')
10.128 + elseif t == "boolean" then
10.129 + outfunc(tostring(val) .. ',\n')
10.130 + else
10.131 + outfunc('"' .. tostring(val) .. '",\n')
10.132 + end
10.133 + end
10.134 + end
10.135 +end
10.136 +
10.137 +
10.138 +-- Dump table via outfunc
10.139 +
10.140 +function dump(tab, outfunc)
10.141 + return serialize(tab, true, 0, outfunc, { })
10.142 +end
10.143 +
10.144 +
10.145 +-- Encode to UTF-8
10.146 +
10.147 +function encodeutf8(c)
10.148 + if c < 128 then
10.149 + return char(c)
10.150 + elseif c < 2048 then
10.151 + return char(192 + floor(c / 64),
10.152 + 128 + c % 64)
10.153 + elseif c < 65536 then
10.154 + return char(224 + floor(c / 4096),
10.155 + 128 + floor((c % 4096) / 64),
10.156 + 128 + c % 64)
10.157 + elseif c < 2097152 then
10.158 + return char(240 + floor(c / 262144),
10.159 + 128 + floor((c % 262144) / 4096),
10.160 + 128 + floor((c % 4096) / 64),
10.161 + 128 + c % 64)
10.162 + elseif c < 67108864 then
10.163 + return char(248 + floor(c / 16777216),
10.164 + 128 + floor((c % 16777216) / 262144),
10.165 + 128 + floor((c % 262144) / 4096),
10.166 + 128 + floor((c % 4096) / 64),
10.167 + 128 + c % 64)
10.168 + else
10.169 + return char(252 + floor(c / 1073741824),
10.170 + 128 + floor((c % 1073741824) / 16777216),
10.171 + 128 + floor((c % 16777216) / 262144),
10.172 + 128 + floor((c % 262144) / 4096),
10.173 + 128 + floor((c % 4096) / 64),
10.174 + 128 + c % 64)
10.175 + end
10.176 +end
10.177 +
10.178 +
10.179 +-- Iterate over UTF-8 character values in a string or file
10.180 +
10.181 +function utf8values(s)
10.182 +
10.183 + local readc
10.184 + local i = 0
10.185 + if type(s) == "string" then
10.186 + readc = function()
10.187 + i = i + 1
10.188 + return s:byte(i)
10.189 + end
10.190 + else
10.191 + readc = function()
10.192 + local c = s:read(1)
10.193 + return c and c:byte(1)
10.194 + end
10.195 + end
10.196 +
10.197 + local accu = 0
10.198 + local numa = 0
10.199 + local min
10.200 + local buf
10.201 +
10.202 + return function()
10.203 + local c
10.204 + while true do
10.205 + if buf then
10.206 + c = buf
10.207 + buf = nil
10.208 + else
10.209 + c = readc()
10.210 + end
10.211 + if not c then
10.212 + return
10.213 + end
10.214 + if c == 254 or c == 255 then
10.215 + break
10.216 + end
10.217 + if c < 128 then
10.218 + if numa > 0 then
10.219 + buf = c
10.220 + break
10.221 + end
10.222 + return c
10.223 + elseif c < 192 then
10.224 + if numa == 0 then break end
10.225 + accu = accu * 64 + c - 128
10.226 + numa = numa - 1
10.227 + if numa == 0 then
10.228 + if accu == 0 or accu < min or (accu >= 55296 and accu <= 57343) then
10.229 + break
10.230 + end
10.231 + c = accu
10.232 + accu = 0
10.233 + return c
10.234 + end
10.235 + else
10.236 + if numa > 0 then
10.237 + buf = c
10.238 + break
10.239 + end
10.240 + if c < 224 then
10.241 + min = 128
10.242 + accu = c - 192
10.243 + numa = 1
10.244 + elseif c < 240 then
10.245 + min = 2048
10.246 + accu = c - 224
10.247 + numa = 2
10.248 + elseif c < 248 then
10.249 + min = 65536
10.250 + accu = c - 240
10.251 + numa = 3
10.252 + elseif c < 252 then
10.253 + min = 2097152
10.254 + accu = c - 248
10.255 + numa = 4
10.256 + else
10.257 + min = 67108864
10.258 + accu = c - 252
10.259 + numa = 5
10.260 + end
10.261 + end
10.262 + end
10.263 + accu = 0
10.264 + numa = 0
10.265 + return 65533 -- bad character indicator
10.266 + end
10.267 +end
10.268 +
10.269 +
10.270 +-- Copy file - arguments may be filenames or open filehandles
10.271 +
10.272 +function copyfile(s, d, bufsize)
10.273 + bufsize = bufsize or 4096
10.274 + local success, msg
10.275 + if s and type(s) == "string" then
10.276 + s, msg = open(s, "rb")
10.277 + end
10.278 + if s then
10.279 + if d and type(d) == "string" then
10.280 + d, msg = open(d, "wb")
10.281 + end
10.282 + if d then
10.283 + while true do
10.284 + local b = s:read(bufsize)
10.285 + if not b then
10.286 + break
10.287 + end
10.288 + d:write(b)
10.289 + end
10.290 + success = true
10.291 + d:close()
10.292 + end
10.293 + s:close()
10.294 + end
10.295 + return success, msg
10.296 +end
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/cgi-bin/tek/cgi.lua Mon Feb 12 02:14:11 2007 +0100
11.3 @@ -0,0 +1,73 @@
11.4 +--
11.5 +-- tek.cgi - CGI utilities
11.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
11.7 +-- See copyright notice in COPYRIGHT
11.8 +--
11.9 +
11.10 +require "tek"
11.11 +require "cgilua.post"
11.12 +
11.13 +
11.14 +local utf8values = tek.utf8values
11.15 +local insert, concat = table.insert, table.concat
11.16 +local format, char, byte = string.format, string.char, string.byte
11.17 +local type, tonumber = type, tonumber
11.18 +
11.19 +
11.20 +module "tek.cgi"
11.21 +
11.22 +VERSION = 1
11.23 +REVISION = 3
11.24 +
11.25 +
11.26 +-- Encode for forms (display '<', '>', '&', '"' literally)
11.27 +
11.28 +function encodeform(s)
11.29 + local tab = { }
11.30 + for c in utf8values(s) do
11.31 + if c == 34 then
11.32 + insert(tab, """)
11.33 + elseif c == 38 then
11.34 + insert(tab, "&")
11.35 + elseif c == 60 then
11.36 + insert(tab, "<")
11.37 + elseif c == 62 then
11.38 + insert(tab, ">")
11.39 + elseif c == 91 or c == 93 or c > 126 then
11.40 + insert(tab, format("&#%03d;", c))
11.41 + else
11.42 + insert(tab, char(c))
11.43 + end
11.44 + end
11.45 + return concat(tab)
11.46 +end
11.47 +
11.48 +
11.49 +-- URL encode/decode
11.50 +
11.51 +function encodeurl(s, keep)
11.52 + local matchset = "$&+,/:;=?@" -- reserved chars with special meaning
11.53 + if keep then
11.54 + if type(keep) == "string" then -- delete specified from set:
11.55 + matchset = matchset:gsub("[" .. keep:gsub(".", "%%%1") .. "]", "")
11.56 + elseif keep == true then -- delete whole set:
11.57 + matchset = ""
11.58 + end
11.59 + end
11.60 + -- always substitute unsafe chars:
11.61 + matchset = matchset .. '"<>#%{}|\\^~[]`]'
11.62 + matchset = "[%z\001-\032\127-\255" .. matchset:gsub(".", "%%%1") .. "]"
11.63 + s = s:gsub(matchset, function(c)
11.64 + return format("%%%x", byte(c))
11.65 + end)
11.66 + return s
11.67 +end
11.68 +
11.69 +
11.70 +function decodeurl(s)
11.71 + s = s:gsub("+", " ")
11.72 + return s:gsub("%%(%x%x)", function(h)
11.73 + return char(tonumber(h, 16))
11.74 + end)
11.75 +end
11.76 +
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/cgi-bin/tek/cgi/document.lua Mon Feb 12 02:14:11 2007 +0100
12.3 @@ -0,0 +1,49 @@
12.4 +--
12.5 +-- tek.cgi.document - Determine document attributes
12.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
12.7 +-- See copyright notice in COPYRIGHT
12.8 +--
12.9 +
12.10 +require "tek.cgi.request"
12.11 +
12.12 +local request = tek.cgi.request
12.13 +local open = io.open
12.14 +
12.15 +module "tek.cgi.document"
12.16 +
12.17 +if request.PathTranslated then
12.18 +
12.19 + --
12.20 + -- gather DOCUMENT information ("Name" and "Path"), additionally
12.21 + -- provide a "VirtualPath" that is past the end of the script path.
12.22 + -- "Handler" denotes the script in the path.
12.23 +
12.24 + local pt, vp, f = request.PathTranslated
12.25 +
12.26 + repeat
12.27 + f = open(pt)
12.28 + if f then
12.29 + f:close()
12.30 + -- now matches a filesystem object - the rest is considered 'virtual'
12.31 + break
12.32 + end
12.33 + pt = pt:gsub("^(.*)(/.-)$", function(a, b)
12.34 + vp = b .. (vp or "")
12.35 + return a
12.36 + end)
12.37 + until not pt
12.38 +
12.39 + Handler = pt
12.40 +
12.41 + Name = request.PathInfo
12.42 + if vp then
12.43 + -- isolate document name by matching virtual path at end of string:
12.44 + if Name:sub(-vp:len()) == vp then
12.45 + Name = Name:sub(1, Name:len() - vp:len())
12.46 + end
12.47 + VirtualPath = vp:gsub("^/?(.*)$", "%1")
12.48 + end
12.49 +
12.50 + Path = pt:gsub("^(.*/).-$", "%1")
12.51 +
12.52 +end
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/cgi-bin/tek/cgi/header.lua Mon Feb 12 02:14:11 2007 +0100
13.3 @@ -0,0 +1,16 @@
13.4 +--
13.5 +-- tek.cgi.header - Collect header variables
13.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
13.7 +-- See copyright notice in COPYRIGHT
13.8 +--
13.9 +
13.10 +local getenv = os.getenv
13.11 +local find = string.find
13.12 +
13.13 +module "tek.cgi.header"
13.14 +
13.15 +Accept = getenv("HTTP_ACCEPT")
13.16 +UserAgent = getenv("HTTP_USER_AGENT")
13.17 +Referer = getenv("HTTP_REFERER")
13.18 +Host = getenv("HTTP_HOST")
13.19 +CookieString = getenv("HTTP_COOKIE")
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/cgi-bin/tek/cgi/header/cookies.lua Mon Feb 12 02:14:11 2007 +0100
14.3 @@ -0,0 +1,25 @@
14.4 +--
14.5 +-- tek/cgi/header/cookies.lua - Collect cookies
14.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
14.7 +-- See copyright notice in COPYRIGHT
14.8 +--
14.9 +
14.10 +require "tek.cgi.header"
14.11 +
14.12 +local find, cookiestring, getfenv =
14.13 + string.find, tek.cgi.header.CookieString, getfenv
14.14 +
14.15 +module "tek.cgi.header.cookies"
14.16 +
14.17 +if cookiestring then
14.18 + local self = getfenv()
14.19 + local pos, name, id = 0
14.20 + while true do
14.21 + _, pos, name, id = find(cookiestring,
14.22 + "[%s]*([%w_]+)=([%w%.%-%+%%_]+)[;%s]*", pos + 1)
14.23 + if not pos then
14.24 + break
14.25 + end
14.26 + self[name] = id
14.27 + end
14.28 +end
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
15.2 +++ b/cgi-bin/tek/cgi/request.lua Mon Feb 12 02:14:11 2007 +0100
15.3 @@ -0,0 +1,29 @@
15.4 +--
15.5 +-- tek.cgi.request - Collect request variables
15.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
15.7 +-- See copyright notice in COPYRIGHT
15.8 +--
15.9 +
15.10 +require "tek.cgi"
15.11 +
15.12 +local getenv = os.getenv
15.13 +local tonumber = tonumber
15.14 +
15.15 +module "tek.cgi.request"
15.16 +
15.17 +Protocol = getenv("SERVER_PROTOCOL")
15.18 +Port = tonumber(getenv("SERVER_PORT"))
15.19 +Method = getenv("REQUEST_METHOD")
15.20 +PathInfo = getenv("PATH_INFO")
15.21 +PathTranslated = getenv("PATH_TRANSLATED")
15.22 +ScriptName = getenv("SCRIPT_NAME")
15.23 +QueryString = getenv("QUERY_STRING")
15.24 +RemoteHost = getenv("REMOTE_HOST")
15.25 +RemoteAddr = getenv("REMOTE_ADDR")
15.26 +AuthType = getenv("AUTH_TYPE")
15.27 +RemoteUser = getenv("REMOTE_USER")
15.28 +RemoteIdent = getenv("REMOTE_IDENT")
15.29 +ContentType = getenv("CONTENT_TYPE")
15.30 +ContentLength = getenv("CONTENT_LENGTH")
15.31 +DocumentName = getenv("DOCUMENT_NAME")
15.32 +UniqueID = getenv("UNIQUE_ID")
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/cgi-bin/tek/cgi/request/args.lua Mon Feb 12 02:14:11 2007 +0100
16.3 @@ -0,0 +1,33 @@
16.4 +--
16.5 +-- tek.cgi.request.args - Collect CGI arguments
16.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
16.7 +-- See copyright notice in COPYRIGHT
16.8 +--
16.9 +
16.10 +require "tek.cgi"
16.11 +require "tek.cgi.request"
16.12 +require "cgilua.post"
16.13 +
16.14 +local request, decodeurl, parsedata, read_stdin, pairs, getfenv =
16.15 + tek.cgi.request, tek.cgi.decodeurl, cgilua.post.parsedata, io.read, pairs, getfenv
16.16 +
16.17 +module "tek.cgi.request.args"
16.18 +
16.19 +local self = getfenv()
16.20 +
16.21 +if request.Method == "GET" then
16.22 + request.QueryString:gsub("([^&=]+)=([^&=]+)", function(key, value)
16.23 + self[key] = decodeurl(value)
16.24 + end)
16.25 +elseif request.Method == "POST" then
16.26 + local foo = {}
16.27 + parsedata {
16.28 + read = read_stdin,
16.29 + discardinput = nil,
16.30 + content_type = request.ContentType,
16.31 + content_length = request.ContentLength,
16.32 + maxinput = 1048575,
16.33 + maxfilesize = 524288,
16.34 + args = self
16.35 + }
16.36 +end
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
17.2 +++ b/cgi-bin/tek/cgi/session.lua Mon Feb 12 02:14:11 2007 +0100
17.3 @@ -0,0 +1,49 @@
17.4 +--
17.5 +-- tek.cgi.session - Session state handler
17.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
17.7 +-- See copyright notice in COPYRIGHT
17.8 +--
17.9 +
17.10 +require "tek"
17.11 +require "tek.util"
17.12 +require "tek.cgi.request"
17.13 +
17.14 +
17.15 +local format, byte = string.format, string.byte
17.16 +local expire, source, dump, request =
17.17 + tek.util.expire, tek.source, tek.dump, tek.cgi.request
17.18 +local open, remove = io.open, os.remove
17.19 +local unpack, assert = unpack, assert
17.20 +
17.21 +
17.22 +module "tek.cgi.session"
17.23 +
17.24 +
17.25 +function save()
17.26 + local f = open(filename, "wb")
17.27 + assert(f, "Failed to open session file for writing")
17.28 + dump(data, function(...)
17.29 + f:write(unpack(arg))
17.30 + end)
17.31 + f:close()
17.32 +end
17.33 +
17.34 +
17.35 +function delete()
17.36 + remove(filename)
17.37 +end
17.38 +
17.39 +
17.40 +function init(sessiondir, sessionid, maxage)
17.41 + id = sessionid or request.UniqueID
17.42 + assert(id, "Could not determine session ID")
17.43 + filename = sessiondir .. "/" .. id:gsub("(.)", function(a)
17.44 + return format("%02x", byte(a))
17.45 + end)
17.46 + -- remove non-dotted files (expired sessions) from sessions dir:
17.47 + expire(sessiondir, "[^.]%S+", maxage)
17.48 + -- load session state:
17.49 + data = source(filename) or { }
17.50 + -- write back session ID in request args:
17.51 + request.args.session = id
17.52 +end
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
18.2 +++ b/cgi-bin/tek/posix.c Mon Feb 12 02:14:11 2007 +0100
18.3 @@ -0,0 +1,414 @@
18.4 +
18.5 +#include <stdlib.h>
18.6 +#include <string.h>
18.7 +#include <sys/types.h>
18.8 +#include <sys/stat.h>
18.9 +#include <unistd.h>
18.10 +#include <dirent.h>
18.11 +#include <errno.h>
18.12 +#include <lua.h>
18.13 +#include <lualib.h>
18.14 +#include <lauxlib.h>
18.15 +
18.16 +
18.17 +#define DIRCLASSNAME "dir*"
18.18 +
18.19 +
18.20 +static void
18.21 +setfield(lua_State *L, const char *attr, const char *key, lua_Integer n)
18.22 +{
18.23 + if (attr == NULL || strcmp(attr, key) == 0)
18.24 + lua_pushinteger(L, n);
18.25 + if (attr == NULL)
18.26 + lua_setfield(L, -2, key);
18.27 +}
18.28 +
18.29 +static int
18.30 +setstat(lua_State *L, int result, struct stat *s, const char *attr)
18.31 +{
18.32 + if (result != 0)
18.33 + {
18.34 + lua_pushnil(L);
18.35 + lua_pushstring(L, strerror(errno));
18.36 + return 2;
18.37 + }
18.38 +
18.39 + if (attr == NULL)
18.40 + lua_newtable(L);
18.41 +
18.42 + setfield(L, attr, "dev", s->st_dev);
18.43 + setfield(L, attr, "ino", s->st_ino);
18.44 +
18.45 + if (attr == NULL || strcmp(attr, "mode") == 0)
18.46 + {
18.47 + if (S_ISREG(s->st_mode))
18.48 + lua_pushstring(L, "file");
18.49 + else if (S_ISDIR(s->st_mode))
18.50 + lua_pushstring(L, "directory");
18.51 + else if (S_ISLNK(s->st_mode))
18.52 + lua_pushstring(L, "link");
18.53 + else if (S_ISSOCK(s->st_mode))
18.54 + lua_pushstring(L, "socket");
18.55 + else if (S_ISFIFO(s->st_mode))
18.56 + lua_pushstring(L, "named pipe");
18.57 + else if (S_ISCHR(s->st_mode))
18.58 + lua_pushstring(L, "char device");
18.59 + else if (S_ISBLK(s->st_mode))
18.60 + lua_pushstring(L, "block device");
18.61 + else
18.62 + lua_pushstring(L, "other");
18.63 + if (attr == NULL)
18.64 + lua_setfield(L, -2, "mode");
18.65 + }
18.66 +
18.67 + setfield(L, attr, "nlink", s->st_nlink);
18.68 + setfield(L, attr, "uid", s->st_uid);
18.69 + setfield(L, attr, "gid", s->st_gid);
18.70 + setfield(L, attr, "rdev", s->st_rdev);
18.71 + setfield(L, attr, "access", s->st_atime);
18.72 + setfield(L, attr, "modifications", s->st_mtime);
18.73 + setfield(L, attr, "change", s->st_ctime);
18.74 + setfield(L, attr, "size", s->st_ctime);
18.75 + setfield(L, attr, "blocks", s->st_blocks);
18.76 + setfield(L, attr, "blksize", s->st_blksize);
18.77 +
18.78 + return 1;
18.79 +}
18.80 +
18.81 +
18.82 +static int
18.83 +posix_stat(lua_State *L)
18.84 +{
18.85 + const char *path = luaL_checkstring(L, 1);
18.86 + const char *attr = luaL_optstring(L, 2, NULL);
18.87 + struct stat s;
18.88 + return setstat(L, stat(path, &s), &s, attr);
18.89 +}
18.90 +
18.91 +
18.92 +static int
18.93 +posix_lstat(lua_State *L)
18.94 +{
18.95 + const char *path = luaL_checkstring(L, 1);
18.96 + const char *attr = luaL_optstring(L, 2, NULL);
18.97 + struct stat s;
18.98 + return setstat(L, lstat(path, &s), &s, attr);
18.99 +}
18.100 +
18.101 +
18.102 +static int
18.103 +posix_opendir(lua_State *L)
18.104 +{
18.105 + const char *path = luaL_checkstring(L, 1);
18.106 + DIR **pdir = lua_newuserdata(L, sizeof(void *));
18.107 + *pdir = NULL;
18.108 + lua_pushvalue(L, lua_upvalueindex(2)); /* class metatable */
18.109 + /* attach metatable to the userdata object */
18.110 + lua_setmetatable(L, -2); /* s: udata */
18.111 + /* create class instance */
18.112 + *pdir = opendir(path);
18.113 + if (*pdir == NULL)
18.114 + {
18.115 + lua_pushnil(L);
18.116 + lua_pushstring(L, strerror(errno));
18.117 + return 2;
18.118 + }
18.119 + return 1;
18.120 +}
18.121 +
18.122 +
18.123 +static DIR **
18.124 +getinstptr(lua_State *L, int narg, const char *classname)
18.125 +{
18.126 + DIR **pinst = luaL_checkudata(L, narg, classname);
18.127 + if (*pinst) return pinst;
18.128 + luaL_argerror(L, narg, "Closed handle");
18.129 + return NULL;
18.130 +}
18.131 +
18.132 +
18.133 +static int
18.134 +posix_closedir(lua_State *L)
18.135 +{
18.136 + DIR **pdir = getinstptr(L, 1, DIRCLASSNAME);
18.137 + closedir(*pdir);
18.138 + *pdir = NULL;
18.139 + return 0;
18.140 +}
18.141 +
18.142 +
18.143 +static int
18.144 +posix_readdir(lua_State *L)
18.145 +{
18.146 + DIR *dir = *getinstptr(L, 1, DIRCLASSNAME);
18.147 + struct dirent *de = readdir(dir);
18.148 + if (de)
18.149 + lua_pushstring(L, de->d_name);
18.150 + else
18.151 + lua_pushnil(L);
18.152 + return 1;
18.153 +}
18.154 +
18.155 +
18.156 +static int
18.157 +posix_readlink(lua_State *L)
18.158 +{
18.159 + const char *path = luaL_checkstring(L, 1);
18.160 + char buf[PATH_MAX + 1];
18.161 + ssize_t len = readlink(path, buf, sizeof(buf) - 1);
18.162 + if (len < 0)
18.163 + {
18.164 + lua_pushnil(L);
18.165 + lua_pushstring(L, strerror(errno));
18.166 + return 2;
18.167 + }
18.168 + buf[len] = 0;
18.169 + lua_pushstring(L, buf);
18.170 + return 1;
18.171 +}
18.172 +
18.173 +
18.174 +static int
18.175 +posix_mkdir(lua_State *L)
18.176 +{
18.177 + const char *path = luaL_checkstring(L, 1);
18.178 + if (mkdir(path, 0775) == 0)
18.179 + {
18.180 + lua_pushboolean(L, 1);
18.181 + return 1;
18.182 + }
18.183 + lua_pushnil(L);
18.184 + lua_pushstring(L, strerror(errno));
18.185 + return 2;
18.186 +}
18.187 +
18.188 +
18.189 +static int
18.190 +posix_symlink(lua_State *L)
18.191 +{
18.192 + const char *oldpath = luaL_checkstring(L, 1);
18.193 + const char *newpath = luaL_checkstring(L, 2);
18.194 + if (symlink(oldpath, newpath) == 0)
18.195 + {
18.196 + lua_pushboolean(L, 1);
18.197 + return 1;
18.198 + }
18.199 + lua_pushnil(L);
18.200 + lua_pushstring(L, strerror(errno));
18.201 + return 2;
18.202 +}
18.203 +
18.204 +
18.205 +/*
18.206 +** POSIX realpath() is broken. We can do better
18.207 +*/
18.208 +
18.209 +static int
18.210 +resolvepath(const char *src, char *dest)
18.211 +{
18.212 + int len = strlen(src);
18.213 + const char *sp = src + len;
18.214 + char *dp = dest;
18.215 + int dc = 0, slc = 0, eac = 0, wc = 0;
18.216 + int i, c;
18.217 +
18.218 + while (len--)
18.219 + {
18.220 + c = *(--sp);
18.221 + switch (c)
18.222 + {
18.223 + case '/':
18.224 + if (dc == 2)
18.225 + eac++;
18.226 + dc = 0;
18.227 + slc = 1;
18.228 + wc = 0;
18.229 + break;
18.230 +
18.231 + case '.':
18.232 + if (slc)
18.233 + {
18.234 + dc++;
18.235 + break;
18.236 + }
18.237 + /* fallthru: */
18.238 +
18.239 + default:
18.240 + if (wc)
18.241 + break;
18.242 +
18.243 + if (slc)
18.244 + {
18.245 + slc = 0;
18.246 +
18.247 + if (eac > 0)
18.248 + {
18.249 + /* resolve one eatcount */
18.250 + eac--;
18.251 + /* now wait for next path part */
18.252 + wc = 1;
18.253 + break;
18.254 + }
18.255 +
18.256 + *dp++ = '/';
18.257 + }
18.258 +
18.259 + while (dc == 2 || dc == 1)
18.260 + {
18.261 + *dp++ = '.';
18.262 + dc--;
18.263 + }
18.264 + dc = 0;
18.265 +
18.266 + *dp++ = c;
18.267 + break;
18.268 + }
18.269 + }
18.270 +
18.271 + /* unresolved eatcount */
18.272 + if (eac)
18.273 + return 0;
18.274 +
18.275 + /* resolve remaining slash */
18.276 + if (slc)
18.277 + *dp++ = '/';
18.278 +
18.279 + *dp = 0;
18.280 +
18.281 + len = dp - dest;
18.282 + for (i = 0; i < len / 2; ++i)
18.283 + {
18.284 + char t = dest[i];
18.285 + dest[i] = dest[len - i - 1];
18.286 + dest[len - i - 1] = t;
18.287 + }
18.288 +
18.289 + return 1;
18.290 +}
18.291 +
18.292 +
18.293 +static int
18.294 +posix_abspath(lua_State *L)
18.295 +{
18.296 + const char *path = luaL_checkstring(L, 1);
18.297 + char *pwd = NULL;
18.298 + size_t pwdsize = 16;
18.299 +
18.300 + for (;;)
18.301 + {
18.302 + char *newpwd = realloc(pwd, pwdsize);
18.303 + if (newpwd == NULL)
18.304 + break;
18.305 +
18.306 + pwd = newpwd;
18.307 + if (getcwd(pwd, pwdsize))
18.308 + {
18.309 + size_t len1 = strlen(pwd);
18.310 + size_t len2 = strlen(path);
18.311 + char *srcpath = malloc(len1 + 1 + len2 + 1);
18.312 + char *dstpath = malloc(len1 + 1 + len2 + 1);
18.313 + if (srcpath && dstpath)
18.314 + {
18.315 + int res;
18.316 +
18.317 + strcpy(srcpath, pwd);
18.318 + free(pwd);
18.319 + srcpath[len1] = '/';
18.320 + strcpy(srcpath + len1 + 1, path);
18.321 + res = resolvepath(srcpath, dstpath);
18.322 + free(srcpath);
18.323 +
18.324 + if (res)
18.325 + {
18.326 + lua_pushstring(L, dstpath);
18.327 + free(dstpath);
18.328 + return 1;
18.329 + }
18.330 +
18.331 + free(dstpath);
18.332 + lua_pushnil(L);
18.333 + lua_pushstring(L, "Not a valid path");
18.334 + return 2;
18.335 + }
18.336 +
18.337 + free(srcpath);
18.338 + free(dstpath);
18.339 + break;
18.340 + }
18.341 +
18.342 + if (errno == ERANGE)
18.343 + {
18.344 + pwdsize <<= 1;
18.345 + continue;
18.346 + }
18.347 +
18.348 + lua_pushnil(L);
18.349 + lua_pushstring(L, strerror(errno));
18.350 + return 2;
18.351 + }
18.352 +
18.353 + free(pwd);
18.354 + luaL_error(L, "Out of memory");
18.355 + return 0;
18.356 +}
18.357 +
18.358 +
18.359 +static const luaL_Reg lib[] =
18.360 +{
18.361 + { "stat", posix_stat },
18.362 + { "lstat", posix_lstat },
18.363 + { "opendir", posix_opendir },
18.364 + { "closedir", posix_closedir },
18.365 + { "readdir", posix_readdir },
18.366 + { "mkdir", posix_mkdir },
18.367 + { "readlink", posix_readlink },
18.368 + { "symlink", posix_symlink },
18.369 + { "abspath", posix_abspath },
18.370 + { NULL, NULL }
18.371 +};
18.372 +
18.373 +
18.374 +static const luaL_Reg methods[] =
18.375 +{
18.376 + {"read", posix_readdir },
18.377 + {"close", posix_closedir },
18.378 + {"__gc", posix_closedir },
18.379 + {NULL, NULL}
18.380 +};
18.381 +
18.382 +
18.383 +static void
18.384 +addclass(lua_State *L, const char *libname, const char *classname,
18.385 + luaL_Reg *functions, luaL_Reg *methods, void *userdata)
18.386 +{
18.387 + luaL_newmetatable(L, classname); /* classtab */
18.388 + lua_pushliteral(L, "__index"); /* classtab, "__index" */
18.389 +
18.390 + /* insert self: classtab.__index = classtab */
18.391 + lua_pushvalue(L, -2); /* classtab, "__index", classtab */
18.392 + lua_rawset(L, -3); /* classtab */
18.393 +
18.394 + /* insert methods. consume 1 userdata. do not create a new tab */
18.395 + lua_pushlightuserdata(L, userdata); /* classtab, userdata */
18.396 + luaL_openlib(L, NULL, methods, 1); /* classtab */
18.397 +
18.398 + /* first upvalue: userdata */
18.399 + lua_pushlightuserdata(L, userdata); /* classtab, userdata */
18.400 +
18.401 + /* duplicate table argument to be used as second upvalue for cclosure */
18.402 + lua_pushvalue(L, -2); /* classtab, userdata, classtab */
18.403 +
18.404 + /* insert functions */
18.405 + luaL_openlib(L, libname, functions, 2); /* classtab, libtab */
18.406 +
18.407 + /* adjust stack */
18.408 + lua_pop(L, 2);
18.409 +}
18.410 +
18.411 +
18.412 +int luaopen_tek_posix(lua_State *L)
18.413 +{
18.414 + addclass(L, "tek.posix", DIRCLASSNAME,
18.415 + (luaL_Reg *) lib, (luaL_Reg *) methods, NULL);
18.416 + return 0;
18.417 +}
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
19.2 +++ b/cgi-bin/tek/util.lua Mon Feb 12 02:14:11 2007 +0100
19.3 @@ -0,0 +1,60 @@
19.4 +--
19.5 +-- tek.util - Utilities
19.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
19.7 +-- See copyright notice in COPYRIGHT
19.8 +--
19.9 +
19.10 +require "tek"
19.11 +require "tek.posix"
19.12 +
19.13 +
19.14 +local error, assert = error, assert
19.15 +local opendir, stat = tek.posix.opendir, tek.posix.stat
19.16 +local time, remove = os.time, os.remove
19.17 +
19.18 +
19.19 +module "tek.util"
19.20 +
19.21 +
19.22 +-- Directory iterator
19.23 +
19.24 +function readdir(path)
19.25 + assert(path)
19.26 + local d, msg = opendir(path)
19.27 + if not d then
19.28 + error("Cannot open directory `" .. path .. "' : " .. msg)
19.29 + end
19.30 + return function()
19.31 + local e
19.32 + repeat
19.33 + e = d:read()
19.34 + until e ~= "." and e ~= ".."
19.35 + return e
19.36 + end
19.37 +end
19.38 +
19.39 +
19.40 +-- Remove directory entries matching a pattern
19.41 +-- whose atime is older than maxtime seconds
19.42 +
19.43 +function expire(dir, pat, maxtime)
19.44 + if stat(dir, "mode") == "directory" then
19.45 + maxtime = maxtime or 600
19.46 + local nowtime = time()
19.47 + for e in readdir(dir) do
19.48 + if not pat or e:match(pat) then
19.49 + local fname = dir .. "/" .. e
19.50 + -- assuming here that if an entry is a dangling softlink
19.51 + -- (in which case stat() returns nil), it can be deleted anyway
19.52 + local atime = stat(fname, "access")
19.53 + if not atime or (nowtime - atime > maxtime) then
19.54 + local res, msg = remove(fname)
19.55 + if not res then
19.56 + error("Failed to delete item `" .. fname .. "' : " .. msg)
19.57 + end
19.58 + end
19.59 + end
19.60 + end
19.61 + return true
19.62 + end
19.63 +end
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
20.2 +++ b/cgi-bin/tek/web.lua Mon Feb 12 02:14:11 2007 +0100
20.3 @@ -0,0 +1,194 @@
20.4 +--
20.5 +-- tek.web - HTML utilities
20.6 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
20.7 +-- See copyright notice in COPYRIGHT
20.8 +--
20.9 +
20.10 +require "tek"
20.11 +require "tek.cgi"
20.12 +
20.13 +
20.14 +local cgi = tek.cgi
20.15 +local stdout = io.stdout
20.16 +local table, string, unpack, ipairs = table, string, unpack, ipairs
20.17 +
20.18 +
20.19 +module "tek.web"
20.20 +
20.21 +_VERSION = 2
20.22 +_REVISION = 0
20.23 +
20.24 +
20.25 +local function getargs(...)
20.26 + local args2 = { } -- { { name = key, value = val }, ... }
20.27 + for _, a in ipairs(arg) do
20.28 + local key, val = a:match("^(%w+)=(.*)$")
20.29 + if key and val then
20.30 + if val == "" then
20.31 + -- "key=" removes existing argument of given key
20.32 + for i, a2 in ipairs(args2) do
20.33 + if a2 == key.name then
20.34 + table.remove(args2, i)
20.35 + break
20.36 + end
20.37 + end
20.38 + else
20.39 + -- "arg=val" sets/overrides argument key
20.40 + table.insert(args2, { ["name"] = key, ["value"] = val })
20.41 + end
20.42 + elseif cgi.request.args[a] then
20.43 + -- just "arg" propagates existing REQUEST.Args[arg]
20.44 + table.insert(args2, { ["name"] = a, ["value"] = cgi.request.args[a] })
20.45 + end
20.46 + end
20.47 + local out = { }
20.48 + for i, arg in ipairs(args2) do
20.49 + if i == 1 then
20.50 + table.insert(out, "?")
20.51 + else
20.52 + table.insert(out, "&")
20.53 + end
20.54 + table.insert(out, arg.name)
20.55 + table.insert(out, "=")
20.56 + table.insert(out, cgi.encodeurl(arg.value))
20.57 + end
20.58 + return table.concat(out)
20.59 +end
20.60 +
20.61 +
20.62 +function gethref(doc, ...)
20.63 + local key, val
20.64 + local first = true
20.65 + local t = { }
20.66 + local url, anch = doc:match("^(.+)(#.+)$")
20.67 + if url and anch then
20.68 + table.insert(t, url)
20.69 + table.insert(t, getargs(unpack(arg)))
20.70 + table.insert(t, anch)
20.71 + else
20.72 + table.insert(t, doc)
20.73 + table.insert(t, getargs(unpack(arg)))
20.74 + end
20.75 + return table.concat(t)
20.76 +end
20.77 +
20.78 +
20.79 +outbuf = { }
20.80 +headerbuf = { }
20.81 +
20.82 +
20.83 +-- The output function gathers the output in a buffer which needs
20.84 +-- to get flushed (or can be discarded)
20.85 +
20.86 +function out(s)
20.87 + table.insert(outbuf, s)
20.88 +end
20.89 +
20.90 +
20.91 +function setheader(s)
20.92 + table.insert(headerbuf, 1, s)
20.93 +end
20.94 +
20.95 +
20.96 +function discard()
20.97 + headerbuf = { }
20.98 + outbuf = { }
20.99 +end
20.100 +
20.101 +
20.102 +function htmlerr(msg)
20.103 + stdout:write("Content-Type: text/html\n\n")
20.104 + stdout:write("<html><body>\n")
20.105 + stdout:write("<div><h2>Error</h2>Malformed XHTML : "..msg.."</div>\n")
20.106 + stdout:write("</body></html>\n")
20.107 +end
20.108 +
20.109 +
20.110 +function flush(tidy)
20.111 +
20.112 + stdout:write(unpack(headerbuf))
20.113 +
20.114 + if not tidy then
20.115 +
20.116 + stdout:write(unpack(outbuf))
20.117 +
20.118 + else
20.119 +
20.120 + -- check for valid xhtml and create tidy formatting
20.121 +
20.122 + local stack = { }
20.123 + local out = { }
20.124 + local curtag = { }
20.125 + local indent = 0
20.126 +
20.127 + table.concat(outbuf):gsub("(.-)(%b<>)", function(a, b)
20.128 +
20.129 + local ta, t, te = b:match("^<%s*([%!%/%?]?)%s*(.-)%s*([/%?]?)%s*>$")
20.130 + local tag = t:match("^(%a%w*)")
20.131 +
20.132 + if curtag.literal then
20.133 + table.insert(out, a)
20.134 + elseif a:match("%S") then
20.135 + local outl = { }
20.136 + a:gsub("(%S+)", function(a) table.insert(outl, a) end)
20.137 + outl = table.concat(outl, " ")
20.138 + if curtag.nobreak then
20.139 + table.insert(out, outl)
20.140 + else
20.141 + table.insert(out, string.rep("\t", indent))
20.142 + table.insert(out, outl)
20.143 + table.insert(out, "\n")
20.144 + end
20.145 + end
20.146 +
20.147 + if t ~= "" then
20.148 + if ta == "" and te == "" then
20.149 + table.insert(stack, curtag)
20.150 + curtag = { tag = tag,
20.151 + literal = tag == "pre" or tag == "textarea",
20.152 + nobreak = tag == "a" or tag == "span" or (tag and tag:match("^h%d$")) }
20.153 + table.insert(out, string.rep("\t", indent))
20.154 + table.insert(out, "<" .. t .. ">")
20.155 + if not curtag.literal and not curtag.nobreak then
20.156 + table.insert(out, "\n")
20.157 + end
20.158 + indent = indent + 1
20.159 + elseif ta == "/" and te == "" then
20.160 + if curtag.tag ~= tag then
20.161 + if not msg then
20.162 + msg = "Expected : </" .. (tag or "nil") .. ">, found : </" .. (curtag.tag or "nil") .. ">"
20.163 + end
20.164 + end
20.165 + indent = indent - 1
20.166 + if not curtag.literal and not curtag.nobreak then
20.167 + table.insert(out, string.rep("\t", indent))
20.168 + end
20.169 + table.insert(out, "</" .. t .. ">\n")
20.170 + curtag = table.remove(stack)
20.171 + elseif ta == "" and te == "/" then
20.172 + table.insert(out, string.rep("\t", indent))
20.173 + table.insert(out, "<" .. t .. " />\n")
20.174 + elseif ta == "!" and te == "" then
20.175 + table.insert(out, string.rep("\t", indent))
20.176 + table.insert(out, "<!" .. t .. ">\n")
20.177 + elseif ta == "?" and te == "?" then
20.178 + table.insert(out, string.rep("\t", indent))
20.179 + table.insert(out, "<?" .. t .. "?>\n")
20.180 + end
20.181 + end
20.182 +
20.183 + end)
20.184 +
20.185 + if msg then
20.186 + htmlerr(msg)
20.187 + else
20.188 + stdout:write(table.concat(out))
20.189 + end
20.190 +
20.191 + end
20.192 +
20.193 + headerbuf = { }
20.194 + outbuf = { }
20.195 +
20.196 +end
20.197 +
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
21.2 +++ b/cgi-bin/tek/web/include.c Mon Feb 12 02:14:11 2007 +0100
21.3 @@ -0,0 +1,385 @@
21.4 +
21.5 +#include <ctype.h>
21.6 +#include <stdlib.h>
21.7 +#include <string.h>
21.8 +#include <lua.h>
21.9 +#include <lualib.h>
21.10 +#include <lauxlib.h>
21.11 +
21.12 +
21.13 +#define topfile(L) ((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE))
21.14 +
21.15 +
21.16 +static FILE *tofile (lua_State *L) {
21.17 + FILE **f = topfile(L);
21.18 + if (*f == NULL)
21.19 + luaL_error(L, "attempt to use a closed file");
21.20 + return *f;
21.21 +}
21.22 +
21.23 +
21.24 +static unsigned char *encodeutf8(unsigned char *buf, int c)
21.25 +{
21.26 + if (c < 128)
21.27 + {
21.28 + *buf++ = c;
21.29 + }
21.30 + else if (c < 2048)
21.31 + {
21.32 + *buf++ = 0xc0 + (c >> 6);
21.33 + *buf++ = 0x80 + (c & 0x3f);
21.34 + }
21.35 + else if (c < 65536)
21.36 + {
21.37 + *buf++ = 0xe0 + (c >> 12);
21.38 + *buf++ = 0x80 + ((c & 0xfff) >> 6);
21.39 + *buf++ = 0x80 + (c & 0x3f);
21.40 + }
21.41 + else if (c < 2097152)
21.42 + {
21.43 + *buf++ = 0xf0 + (c >> 18);
21.44 + *buf++ = 0x80 + ((c & 0x3ffff) >> 12);
21.45 + *buf++ = 0x80 + ((c & 0xfff) >> 6);
21.46 + *buf++ = 0x80 + (c & 0x3f);
21.47 + }
21.48 + else if (c < 67108864)
21.49 + {
21.50 + *buf++ = 0xf8 + (c >> 24);
21.51 + *buf++ = 0x80 + ((c & 0xffffff) >> 18);
21.52 + *buf++ = 0x80 + ((c & 0x3ffff) >> 12);
21.53 + *buf++ = 0x80 + ((c & 0xfff) >> 6);
21.54 + *buf++ = 0x80 + (c & 0x3f);
21.55 + }
21.56 + else
21.57 + {
21.58 + *buf++ = 0xfc + (c >> 30);
21.59 + *buf++ = 0x80 + ((c & 0x3fffffff) >> 24);
21.60 + *buf++ = 0x80 + ((c & 0xffffff) >> 18);
21.61 + *buf++ = 0x80 + ((c & 0x3ffff) >> 12);
21.62 + *buf++ = 0x80 + ((c & 0xfff) >> 6);
21.63 + *buf++ = 0x80 + (c & 0x3f);
21.64 + }
21.65 + return buf;
21.66 +}
21.67 +
21.68 +
21.69 +struct utf8reader
21.70 +{
21.71 + int (*readchar)(struct utf8reader *);
21.72 + int accu, numa, min, bufc;
21.73 + const unsigned char *src;
21.74 + size_t srclen;
21.75 + FILE *file;
21.76 + void *udata;
21.77 +};
21.78 +
21.79 +static int readstring(struct utf8reader *rd)
21.80 +{
21.81 + if (rd->srclen == 0)
21.82 + return -1;
21.83 + rd->srclen--;
21.84 + return *rd->src++;
21.85 +}
21.86 +
21.87 +static int readfile(struct utf8reader *rd)
21.88 +{
21.89 + return fgetc(rd->file);
21.90 +}
21.91 +
21.92 +static int readutf8(struct utf8reader *rd)
21.93 +{
21.94 + int c;
21.95 + for (;;)
21.96 + {
21.97 + if (rd->bufc >= 0)
21.98 + {
21.99 + c = rd->bufc;
21.100 + rd->bufc = -1;
21.101 + }
21.102 + else
21.103 + c = rd->readchar(rd);
21.104 +
21.105 + if (c < 0)
21.106 + return c;
21.107 +
21.108 + if (c == 254 || c == 255)
21.109 + break;
21.110 +
21.111 + if (c < 128)
21.112 + {
21.113 + if (rd->numa > 0)
21.114 + {
21.115 + rd->bufc = c;
21.116 + break;
21.117 + }
21.118 + return c;
21.119 + }
21.120 + else if (c < 192)
21.121 + {
21.122 + if (rd->numa == 0)
21.123 + break;
21.124 + rd->accu <<= 6;
21.125 + rd->accu += c - 128;
21.126 + rd->numa--;
21.127 + if (rd->numa == 0)
21.128 + {
21.129 + if (rd->accu == 0 || rd->accu < rd->min ||
21.130 + (rd->accu >= 55296 && rd->accu <= 57343))
21.131 + break;
21.132 + c = rd->accu;
21.133 + rd->accu = 0;
21.134 + return c;
21.135 + }
21.136 + }
21.137 + else
21.138 + {
21.139 + if (rd->numa > 0)
21.140 + {
21.141 + rd->bufc = c;
21.142 + break;
21.143 + }
21.144 +
21.145 + if (c < 224)
21.146 + {
21.147 + rd->min = 128;
21.148 + rd->accu = c - 192;
21.149 + rd->numa = 1;
21.150 + }
21.151 + else if (c < 240)
21.152 + {
21.153 + rd->min = 2048;
21.154 + rd->accu = c - 224;
21.155 + rd->numa = 2;
21.156 + }
21.157 + else if (c < 248)
21.158 + {
21.159 + rd->min = 65536;
21.160 + rd->accu = c - 240;
21.161 + rd->numa = 3;
21.162 + }
21.163 + else if (c < 252)
21.164 + {
21.165 + rd->min = 2097152;
21.166 + rd->accu = c - 248;
21.167 + rd->numa = 4;
21.168 + }
21.169 + else
21.170 + {
21.171 + rd->min = 67108864;
21.172 + rd->accu = c - 252;
21.173 + rd->numa = 5;
21.174 + }
21.175 + }
21.176 + }
21.177 + /* bad char */
21.178 + rd->accu = 0;
21.179 + rd->numa = 0;
21.180 + return 65533;
21.181 +}
21.182 +
21.183 +
21.184 +typedef enum { PARSER_UNDEF = -1, PARSER_HTML, PARSER_OPEN1, PARSER_OPEN2,
21.185 +PARSER_CODE, PARSER_VAR, PARSER_CLOSE } parser_state_t;
21.186 +
21.187 +
21.188 +static unsigned char *outchar(lua_State *L, unsigned char *buf, parser_state_t state, int c)
21.189 +{
21.190 + if (state == PARSER_HTML)
21.191 + {
21.192 + if (c > 127 || c == '[' || c == ']')
21.193 + return buf + sprintf((char *) buf, "&#%02d;", c);
21.194 + }
21.195 + else if (state == PARSER_CODE)
21.196 + {
21.197 + if (c > 127)
21.198 + return encodeutf8(buf, c);
21.199 + }
21.200 + else if (c > 127)
21.201 + luaL_error(L, "Non-ASCII character outside code or HTML context");
21.202 +
21.203 + *buf++ = c;
21.204 + return buf;
21.205 +}
21.206 +
21.207 +
21.208 +struct readdata
21.209 +{
21.210 + /* buffer including " " .. outfunc .. "(": */
21.211 + unsigned char buf0[256];
21.212 + /* pointer into buf0 past outfunc: */
21.213 + unsigned char *buf;
21.214 + /* html+lua parser state: */
21.215 + parser_state_t state;
21.216 + /* utf8 reader state: */
21.217 + struct utf8reader utf8;
21.218 +};
21.219 +
21.220 +
21.221 +static const char *readparsed(lua_State *L, void *udata, size_t *sz)
21.222 +{
21.223 + struct readdata *rd = udata;
21.224 + parser_state_t news = rd->state;
21.225 + int c;
21.226 +
21.227 + while ((c = readutf8(&rd->utf8)) >= 0)
21.228 + {
21.229 + switch (news)
21.230 + {
21.231 + case PARSER_UNDEF:
21.232 + if (c == '<')
21.233 + {
21.234 + news = PARSER_OPEN1;
21.235 + continue;
21.236 + }
21.237 + rd->state = PARSER_HTML;
21.238 + rd->buf[0] = '[';
21.239 + rd->buf[1] = '[';
21.240 + *sz = outchar(L, rd->buf + 2, rd->state, c) - rd->buf0;
21.241 + return (char *) rd->buf0;
21.242 +
21.243 + case PARSER_HTML:
21.244 + if (c == '<')
21.245 + {
21.246 + news = PARSER_OPEN1;
21.247 + continue;
21.248 + }
21.249 + break;
21.250 +
21.251 + case PARSER_OPEN1:
21.252 + if (c == '%')
21.253 + {
21.254 + news = PARSER_OPEN2;
21.255 + continue;
21.256 + }
21.257 + rd->buf[0] = '<';
21.258 + rd->buf[1] = c;
21.259 + *sz = 2;
21.260 + return (char *) rd->buf;
21.261 +
21.262 + case PARSER_OPEN2:
21.263 + if (c == '=')
21.264 + {
21.265 + if (rd->state == PARSER_UNDEF)
21.266 + {
21.267 + rd->state = PARSER_VAR;
21.268 + *sz = rd->buf - rd->buf0;
21.269 + return (char *) rd->buf0;
21.270 + }
21.271 + rd->state = PARSER_VAR;
21.272 + strcpy((char *) rd->buf, "]]..(");
21.273 + *sz = 5;
21.274 + return (char *) rd->buf;
21.275 + }
21.276 +
21.277 + if (rd->state == PARSER_UNDEF)
21.278 + rd->state = PARSER_CODE;
21.279 + else
21.280 + {
21.281 + rd->state = PARSER_CODE;
21.282 + rd->buf[0] = ']';
21.283 + rd->buf[1] = ']';
21.284 + rd->buf[2] = ')';
21.285 + rd->buf[3] = ' ';
21.286 + rd->buf[4] = c;
21.287 + *sz = 5;
21.288 + return (char *) rd->buf;
21.289 + }
21.290 + break;
21.291 +
21.292 + case PARSER_VAR:
21.293 + case PARSER_CODE:
21.294 + if (c == '%')
21.295 + {
21.296 + news = PARSER_CLOSE;
21.297 + continue;
21.298 + }
21.299 + break;
21.300 +
21.301 + case PARSER_CLOSE:
21.302 + if (c == '>')
21.303 + {
21.304 + if (rd->state == PARSER_CODE)
21.305 + {
21.306 + rd->state = PARSER_HTML;
21.307 + rd->buf[0] = '[';
21.308 + rd->buf[1] = '[';
21.309 + *sz = rd->buf + 2 - rd->buf0;
21.310 + return (char *) rd->buf0;
21.311 + }
21.312 + rd->state = PARSER_HTML;
21.313 + strcpy((char *) rd->buf, " or \"nil\")..[[");
21.314 + *sz = 14;
21.315 + return (char *) rd->buf;
21.316 + }
21.317 + rd->buf[0] = '%';
21.318 + rd->buf[1] = c;
21.319 + *sz = 2;
21.320 + return (char *) rd->buf;
21.321 + }
21.322 +
21.323 + *sz = outchar(L, rd->buf, rd->state, c) - rd->buf;
21.324 + return (char *) rd->buf;
21.325 + }
21.326 +
21.327 + rd->state = PARSER_UNDEF;
21.328 + if (news == PARSER_HTML)
21.329 + {
21.330 + *sz = 4;
21.331 + return "]]) ";
21.332 + }
21.333 +
21.334 + return NULL;
21.335 +}
21.336 +
21.337 +
21.338 +static int load(lua_State *L)
21.339 +{
21.340 + struct readdata rd;
21.341 + const char *outfunc = lua_tostring(L, 2);
21.342 + const char *chunkname = lua_tostring(L, 3);
21.343 + int res;
21.344 +
21.345 + if (lua_isuserdata(L, 1))
21.346 + {
21.347 + rd.utf8.file = tofile(L);
21.348 + rd.utf8.readchar = readfile;
21.349 + }
21.350 + else
21.351 + {
21.352 + rd.utf8.src = (unsigned char *) lua_tolstring(L, 1, &rd.utf8.srclen);
21.353 + rd.utf8.readchar = readstring;
21.354 + }
21.355 +
21.356 + rd.utf8.accu = 0;
21.357 + rd.utf8.numa = 0;
21.358 + rd.utf8.bufc = -1;
21.359 +
21.360 + rd.state = PARSER_UNDEF;
21.361 + strcpy((char *) rd.buf0, " ");
21.362 + strcat((char *) rd.buf0, outfunc);
21.363 + strcat((char *) rd.buf0, "(");
21.364 + rd.buf = rd.buf0 + strlen((char *) rd.buf0);
21.365 +
21.366 + res = lua_load(L, readparsed, &rd, chunkname);
21.367 + if (res == 0)
21.368 + return 1;
21.369 +
21.370 + lua_pushnil(L);
21.371 + lua_insert(L, -2);
21.372 + /* nil, message on stack */
21.373 + return 2;
21.374 +}
21.375 +
21.376 +
21.377 +static const luaL_Reg lib[] =
21.378 +{
21.379 + { "load", load },
21.380 + { NULL, NULL }
21.381 +};
21.382 +
21.383 +
21.384 +int luaopen_tek_web_include(lua_State *L)
21.385 +{
21.386 + luaL_register(L, "tek.web.include", lib);
21.387 + return 0;
21.388 +}
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
22.2 +++ b/cgi-bin/tek/web/markup.lua Mon Feb 12 02:14:11 2007 +0100
22.3 @@ -0,0 +1,691 @@
22.4 +
22.5 +--
22.6 +-- markup.lua - WIKI-style markup module
22.7 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
22.8 +-- See copyright notice in COPYRIGHT
22.9 +--
22.10 +
22.11 +-------------------------------------------------------------------------------
22.12 +
22.13 +require "tek.cgi"
22.14 +
22.15 +--
22.16 +-- iterate over lines in a string
22.17 +--
22.18 +local function lines(s)
22.19 + local pos = 1
22.20 + return function()
22.21 + if pos then
22.22 + local a, e = s:find("[\r]?\n", pos)
22.23 + local o = pos
22.24 + if not a then
22.25 + pos = nil
22.26 + return s:sub(o)
22.27 + else
22.28 + pos = e + 1
22.29 + return s:sub(o, a - 1)
22.30 + end
22.31 + end
22.32 + end
22.33 +end
22.34 +
22.35 +--
22.36 +-- do a generator function by ID
22.37 +--
22.38 +local function doid(state, id, ...)
22.39 + if state.genfunc and state.genfunc[id] then
22.40 + return state.genfunc[id](state, unpack(arg))
22.41 + end
22.42 +end
22.43 +
22.44 +--
22.45 +-- get a SGML/XML tag
22.46 +--
22.47 +local function gettagml(state, id, tag, open)
22.48 + if tag then
22.49 + local tab = { tag }
22.50 + if id == "link" or id == "emphasis" or id == "code" then
22.51 + if not state.brpend and not state.inline then
22.52 + table.insert(tab, 1, string.rep("\t", state.depth))
22.53 + end
22.54 + state.inline = true
22.55 + else
22.56 + if id == "pre" or id == "preline" then
22.57 + state.brpend = nil
22.58 + elseif open or id ~= "preline" then
22.59 + table.insert(tab, 1, string.rep("\t", state.depth))
22.60 + if not open and state.inline then
22.61 + state.brpend = true
22.62 + end
22.63 + end
22.64 + if state.brpend then
22.65 + table.insert(tab, 1, "\n")
22.66 + end
22.67 + if id ~= "preline" or not open then
22.68 + table.insert(tab, "\n")
22.69 + end
22.70 + state.inline = nil
22.71 + end
22.72 + state.brpend = nil
22.73 + return table.concat(tab)
22.74 + end
22.75 +end
22.76 +
22.77 +--
22.78 +-- get a SGML/XML line of text
22.79 +--
22.80 +local function gettextml(state, line, id)
22.81 + local tab = { }
22.82 + if state.brpend then
22.83 + table.insert(tab, "\n")
22.84 + table.insert(tab, string.rep("\t", state.depth))
22.85 + else
22.86 + if id ~= "link" and id ~= "emphasis" and id ~= "code" then
22.87 + if not state.inline then
22.88 + table.insert(tab, string.rep("\t", state.depth))
22.89 + end
22.90 + end
22.91 + end
22.92 + string.gsub(line, "^(%s*)(.-)(%s*)$", function(a, b, c)
22.93 + if a ~= "" then
22.94 + table.insert(tab, " ")
22.95 + end
22.96 + table.insert(tab, b)
22.97 + if c ~= "" then
22.98 + table.insert(tab, " ")
22.99 + end
22.100 + end)
22.101 + state.brpend = true
22.102 + return table.concat(tab)
22.103 +end
22.104 +
22.105 +--
22.106 +-- html out
22.107 +--
22.108 +local gen_html =
22.109 +{
22.110 + out = function(state, ...)
22.111 + state.out(table.concat(arg))
22.112 + end,
22.113 +
22.114 + init = function(state)
22.115 + state.depth = 1
22.116 + return '' -- header
22.117 + end,
22.118 +
22.119 + exit = function(state)
22.120 + return '' -- footer
22.121 + end,
22.122 +
22.123 + gettag = function(state, id, tag, open)
22.124 + return gettagml(state, id, tag, open)
22.125 + end,
22.126 +
22.127 + gettext = function(state, line, id)
22.128 + return tek.cgi.encodeform(gettextml(state, line, id))
22.129 + end,
22.130 +
22.131 + getpre = function(state, line)
22.132 + return tek.cgi.encodeform(line), '\n'
22.133 + end,
22.134 +
22.135 + getcode = function(state, line)
22.136 + return tek.cgi.encodeform(line)
22.137 + end,
22.138 +
22.139 + head = function(state, level, text)
22.140 + return '<h' .. level .. '>', '</h' .. level .. '>'
22.141 + end,
22.142 +
22.143 + argument = function(state, name, text)
22.144 + return ', function()%>', '<%end'
22.145 + end,
22.146 +
22.147 + func = function(state, args)
22.148 + state.is_dynamic_content = true
22.149 + local t = { '<%loona.include("', args[1], '"' }
22.150 + table.remove(args, 1)
22.151 + for _, v in ipairs(args) do
22.152 + table.insert(t, ',' .. v)
22.153 + end
22.154 + return table.concat(t), ')%>'
22.155 + end,
22.156 +
22.157 +-- def = function(state, name)
22.158 +-- state.prebr = nil
22.159 +-- return '<div><dfn>' .. name .. '</dfn>', '</div><p>'
22.160 +-- end,
22.161 +
22.162 + indent = function()
22.163 + return '<div class="indent">', '</div>'
22.164 + end,
22.165 +
22.166 + list = function()
22.167 + return '<ul>', '</ul>'
22.168 + end,
22.169 +
22.170 + item = function(state, bullet)
22.171 + if bullet then
22.172 + return '<li>', '</li>'
22.173 + end
22.174 + return '<li style="list-style-type: none;">', '</li>'
22.175 + end,
22.176 +
22.177 + block = function()
22.178 + return '<div>', '</div>'
22.179 + end,
22.180 +
22.181 + rule = function()
22.182 + return '<hr />'
22.183 + end,
22.184 +
22.185 + pre = function()
22.186 + return '<pre>', '</pre>'
22.187 + end,
22.188 +
22.189 + code = function()
22.190 + return '<code>', '</code>'
22.191 + end,
22.192 +
22.193 + emphasis = function(state, len, text)
22.194 + if len == 1 then
22.195 + return '<em>', '</em>'
22.196 + elseif len == 2 then
22.197 + return '<strong>', '</strong>'
22.198 + else
22.199 + return '<em><strong>', '</strong></em>'
22.200 + end
22.201 + end,
22.202 +
22.203 + link = function(state, link, isurl)
22.204 + if isurl then
22.205 + return '<%=loona.elink("' .. tek.cgi.encodeurl(link, true) .. '", [[', ']])%>'
22.206 + else
22.207 + return '<%=loona.alink("' .. tek.cgi.encodeurl(link, true) .. '", [[', ']])%>'
22.208 + end
22.209 + end,
22.210 +
22.211 + table = function(state, border)
22.212 + return '<table class="text">', '</table>'
22.213 + end,
22.214 +
22.215 + row = function(state)
22.216 + return '<tr>', '</tr>'
22.217 + end,
22.218 +
22.219 + cell = function(state, border)
22.220 + return '<td>', '</td>'
22.221 + end,
22.222 +
22.223 + image = function(state, link)
22.224 + return '<img src="' .. tek.cgi.encodeurl(link, true) .. '" />'
22.225 + end,
22.226 +}
22.227 +
22.228 +--
22.229 +-- parser
22.230 +--
22.231 +local function parse(state, ...)
22.232 +
22.233 + local function doout(id, ...)
22.234 + doid(state, "out", doid(state, id, unpack(arg)))
22.235 + end
22.236 +
22.237 + local function push(id, ...)
22.238 + local opentag, closetag = doid(state, id, unpack(arg))
22.239 + doout("gettag", id, opentag, true)
22.240 + table.insert(state.stack, { id = id, closetag = closetag })
22.241 + state.depth = state.depth + 1
22.242 + end
22.243 +
22.244 + local function pop()
22.245 + local e = table.remove(state.stack)
22.246 + if e then
22.247 + state.depth = state.depth - 1
22.248 + doout("gettag", e.id, e.closetag)
22.249 + return e.id
22.250 + end
22.251 + end
22.252 +
22.253 + local function top()
22.254 + local e = state.stack[table.getn(state.stack)]
22.255 + if e then
22.256 + return e.id
22.257 + end
22.258 + end
22.259 +
22.260 + local function popuntil(...)
22.261 + local i
22.262 + repeat
22.263 + i = pop()
22.264 + for j = 1, arg.n do
22.265 + if i == arg[j] then
22.266 + return
22.267 + end
22.268 + end
22.269 + until not i
22.270 + end
22.271 +
22.272 + local function popwhilenot(...)
22.273 + local i
22.274 + repeat
22.275 + i = top()
22.276 + for j = 1, arg.n do
22.277 + if i == arg[j] then
22.278 + return
22.279 + end
22.280 + end
22.281 + pop()
22.282 + until not i
22.283 + end
22.284 +
22.285 + local function popwhile(...)
22.286 + local cont
22.287 + repeat
22.288 + local id = top()
22.289 + cont = false
22.290 + for i = 1, arg.n do
22.291 + if arg[i] == id then
22.292 + local i = pop()
22.293 + cont = true
22.294 + break
22.295 + end
22.296 + end
22.297 + until not cont
22.298 + end
22.299 +
22.300 + local function popif(...)
22.301 + local i = top()
22.302 + for j = 1, arg.n do
22.303 + if i == arg[j] then
22.304 + pop()
22.305 + return
22.306 + end
22.307 + end
22.308 + end
22.309 +
22.310 + local function checktop(...)
22.311 + local i = top()
22.312 + local ret
22.313 + return table.foreachi(arg, function(_, a)
22.314 + if i == a then
22.315 + return true
22.316 + end
22.317 + end)
22.318 + end
22.319 +
22.320 + local function pushfragment(...)
22.321 + line = table.concat(arg)
22.322 + if not state.context.fragments then
22.323 + state.context.fragments = { }
22.324 + end
22.325 + state.id = (state.id or 0) + 1
22.326 + table.insert(state.context.fragments, 1, { line = line, id = state.id })
22.327 + state.context.topid = state.id
22.328 + end
22.329 +
22.330 + local function popfragment()
22.331 + state.context.firstfragment = nil
22.332 + local frag = table.remove(state.context.fragments, 1)
22.333 + if frag then
22.334 + state.line = frag.line
22.335 + if frag.id == state.context.topid then
22.336 + state.context.firstfragment = state.context.parentfirstfragment
22.337 + end
22.338 + else
22.339 + state.line = nil
22.340 + end
22.341 + return state.line
22.342 + end
22.343 +
22.344 + local function pushcontext(id, line)
22.345 + table.insert(state.cstack, state.context)
22.346 + if id then
22.347 + state.context = { id = id, fragments = { } }
22.348 + pushfragment(line)
22.349 + table.insert(state.cstack, state.context)
22.350 + end
22.351 + end
22.352 +
22.353 + local function popcontext()
22.354 + state.context = table.remove(state.cstack)
22.355 + return state.context
22.356 + end
22.357 +
22.358 + -- parse
22.359 +
22.360 + state.lnr = 1
22.361 + state.previndent = 0
22.362 + state.stack = { }
22.363 + state.depth = 0
22.364 + state.table = nil
22.365 +
22.366 + doout("init")
22.367 + push("document")
22.368 +
22.369 + for line in lines(state.inbuf) do
22.370 +
22.371 + state.indent = 0
22.372 + line = string.gsub(line, "^( *)(.-)%s*$", function(s, t)
22.373 + if t ~= "" then
22.374 + state.indent = string.len(s)
22.375 + else
22.376 + state.indent = state.previndent
22.377 + end
22.378 + return t
22.379 + end)
22.380 +
22.381 + state.istabline = string.find(line, "||", 1, 1)
22.382 + if state.istabline then
22.383 + state.indent = state.previndent
22.384 + end
22.385 + if state.table then
22.386 + popuntil("row")
22.387 + if not state.istabline then
22.388 + popuntil("table")
22.389 + state.table = nil
22.390 + end
22.391 + end
22.392 +
22.393 + if state.indent < state.previndent then
22.394 + local i = state.indent
22.395 + while i < state.previndent do
22.396 + popuntil("indent", "pre")
22.397 + i = i + 1
22.398 + end
22.399 + elseif state.indent == state.previndent + 1 then
22.400 + popif("block")
22.401 + push("indent")
22.402 + elseif state.indent == state.previndent + 2 then
22.403 + push("indent")
22.404 + push("pre")
22.405 +-- elseif state.indent > state.previndent + 2 then
22.406 +-- if not state.dooutput then
22.407 +-- tek.stderr:write("Line " .. state.lnr ..
22.408 +-- ": Large indentation\n")
22.409 +-- end
22.410 + end
22.411 +
22.412 +-- if state.table and state.indent ~= state.tabindent then
22.413 +-- if not state.dooutput then
22.414 +-- tek.stderr:write("Line " .. state.lnr ..
22.415 +-- ": Ambiguous indentation\n")
22.416 +-- state.indent = state.tabindent
22.417 +-- end
22.418 +-- end
22.419 +
22.420 +-- -- def ( SYNOPSIS )
22.421 +--
22.422 +-- if top() ~= "pre" then
22.423 +-- line = string.gsub(line, "^(%u[%u%d%s_]+)::$", function(name)
22.424 +-- popwhile("def", "item", "list", "block", "pre")
22.425 +-- push("def", name)
22.426 +-- return ""
22.427 +-- end)
22.428 +-- end
22.429 +
22.430 + if top() ~= "pre" then
22.431 +
22.432 + -- function ( INCLUDE(name) )
22.433 +
22.434 + line = line:gsub("^%s*INCLUDE%(%s*(.*)%s*%)%s*$", function(line)
22.435 + local args = { }
22.436 + line:gsub(",?([^,]*)", function(a)
22.437 + a = a:match("^%s*(%S.-)%s*$")
22.438 + if a then
22.439 + table.insert(args, a)
22.440 + end
22.441 + end)
22.442 + popwhilenot("document")
22.443 + push("func", args)
22.444 + return ""
22.445 + end)
22.446 +
22.447 + -- argument ( ======== )
22.448 +
22.449 + line = string.gsub(line, "^%s*========+%s*$", function()
22.450 + popwhilenot("func")
22.451 + push("argument")
22.452 + return ""
22.453 + end)
22.454 +
22.455 + -- rule ( ----.. )
22.456 +
22.457 + line = string.gsub(line, "^%s*(%-%-%-%-+)%s*$", function(s)
22.458 + popwhile("block", "list", "item")
22.459 + push("rule")
22.460 + pop()
22.461 + return ""
22.462 + end)
22.463 +
22.464 + end
22.465 +
22.466 + --
22.467 +
22.468 + state.cstack = { }
22.469 + state.context = { parentfirstfragment = true }
22.470 + pushfragment(line)
22.471 + pushcontext()
22.472 +
22.473 + if line == "" then
22.474 + popwhile("block")
22.475 + end
22.476 +
22.477 + while popcontext() do
22.478 + while popfragment() do
22.479 + line = state.line
22.480 +
22.481 + if top() == "pre" then
22.482 +
22.483 + if state.prepend then
22.484 + push("preline")
22.485 + doout("getpre", "")
22.486 + pop()
22.487 + state.prepend = nil
22.488 + end
22.489 +
22.490 + if line == "" then
22.491 + state.prepend = true
22.492 + else
22.493 + push("preline")
22.494 + doout("getpre", line)
22.495 + pop()
22.496 + end
22.497 +
22.498 + else
22.499 + state.prepend = nil
22.500 +
22.501 + if line ~= "" then
22.502 +
22.503 + -- list/item ( * ... )
22.504 +
22.505 + local _, _, b, a = string.find(line, "^([%*%-])%s+(.*)$")
22.506 + if b and a then
22.507 + local inlist = checktop("item", "list")
22.508 + popwhile("item", "block")
22.509 + if not inlist then
22.510 + push("list")
22.511 + end
22.512 + if b == "*" then
22.513 + push("item", true)
22.514 + else
22.515 + push("item")
22.516 + end
22.517 + pushcontext("item", a)
22.518 + break
22.519 + end
22.520 +
22.521 + -- table cells
22.522 +
22.523 + local _, pos, a, b = string.find(line,
22.524 + "^%s*(.-)%s*||%s*(.*)%s*$")
22.525 + if pos then
22.526 + if state.context.id == "table" then
22.527 + popuntil("cell")
22.528 + else
22.529 + state.context.id = "table"
22.530 + if not state.table then
22.531 + state.table = true
22.532 + push("table")
22.533 + end
22.534 + push("row")
22.535 + state.tabindent = state.indent
22.536 + end
22.537 + pushfragment(b)
22.538 + push("cell")
22.539 + pushcontext("cell", a)
22.540 + break
22.541 + elseif state.context.id == "table" then
22.542 + popuntil("cell")
22.543 + push("cell")
22.544 + pushcontext("cell", line)
22.545 + break
22.546 + end
22.547 +
22.548 + -- code
22.549 +
22.550 + local _, _, a, text, b =
22.551 + string.find(line, "^(.-){{(.-)}}(.*)%s*$")
22.552 + if text then
22.553 + if a == "" then
22.554 + if not checktop("block", "item", "cell") then
22.555 + push("block")
22.556 + end
22.557 + if state.context.firstfragment then
22.558 + doout("gettext", "")
22.559 + end
22.560 + push("code")
22.561 + if b == "" then b = " " end
22.562 + pushfragment(b)
22.563 + pushcontext("code", text)
22.564 + else
22.565 + pushfragment("{{", text, "}}", b)
22.566 + pushfragment(a)
22.567 + pushcontext()
22.568 + end
22.569 + break
22.570 + end
22.571 +
22.572 + -- emphasis
22.573 +
22.574 + local _, _, a, x, text, y, b =
22.575 + string.find(line, "^(.-)(''+)(.-)(''+)(.*)$")
22.576 + if text then
22.577 + if a == "" then
22.578 + x, y = string.len(x), string.len(y)
22.579 + local len = math.min(x, y, 4)
22.580 + if not checktop("block", "item", "cell") then
22.581 + push("block")
22.582 + end
22.583 + if state.context.firstfragment then
22.584 + doout("gettext", "")
22.585 + end
22.586 + push("emphasis", len - 1)
22.587 + if b == "" then b = " " end
22.588 + pushfragment(b)
22.589 + pushcontext("emphasis", text)
22.590 + else
22.591 + pushfragment(x, text, y, b)
22.592 + pushfragment(a)
22.593 + pushcontext()
22.594 + end
22.595 + break
22.596 + end
22.597 +
22.598 + -- [[link]], [[title][link]]
22.599 +
22.600 + if state.context.id ~= "link"
22.601 + and state.context.id ~= "code" then
22.602 + local a, title, link, b
22.603 + a, title, link, b = -- [[text][...]]
22.604 + line:match("^(.-)%[%[(.-)%]%[(.-)%]%](.*)%s*$")
22.605 + if not link then -- [[../..]]
22.606 + a, link, b = line:match("^(.-)%[%[(.-)%]%](.*)%s*$")
22.607 + end
22.608 + if link then
22.609 + local isurl = string.match(link, "^%a*:/?/?.*$")
22.610 + if a == "" then
22.611 + if not checktop("block", "item", "cell") then
22.612 + push("block")
22.613 + end
22.614 + if state.context.firstfragment then
22.615 + doout("gettext", "")
22.616 + end
22.617 + push("link", link, isurl)
22.618 + if b == "" then b = " " end
22.619 + pushfragment(b)
22.620 + pushcontext("link", title or link)
22.621 + else
22.622 + pushfragment("[[", title or link, "][", link, "]]", b)
22.623 + pushfragment(a)
22.624 + pushcontext()
22.625 + end
22.626 + break
22.627 + end
22.628 + end
22.629 +
22.630 + -- imglink (@@/images/partner/aladdin.gif@@)
22.631 +
22.632 + line = line:gsub("@@(.-)@@", function(link)
22.633 + push("image", link)
22.634 + pop()
22.635 + return ""
22.636 + end)
22.637 +
22.638 + -- head ( = ... = )
22.639 +
22.640 + line = string.gsub(line, "(=+)%s+(.*)%s+(=+)",
22.641 + function(s1, text, s2)
22.642 + local l = math.min(string.len(s1), string.len(s2), 5)
22.643 + popwhile("block", "item", "list")
22.644 + push("head", l)
22.645 + return text
22.646 + end)
22.647 +
22.648 + -- output
22.649 +
22.650 + if line ~= "" then
22.651 + if not checktop("item", "block", "cell", "pre",
22.652 + "head", "emphasis", "link", "code") then
22.653 + popwhile("item", "list", "pre", "code")
22.654 + push("block")
22.655 + end
22.656 + if top() == "code" then
22.657 + doout("getcode", line, top())
22.658 + else
22.659 + doout("gettext", line, top())
22.660 + end
22.661 + end
22.662 + popif("emphasis", "head", "link", "code")
22.663 + end
22.664 + end
22.665 + end
22.666 + end
22.667 +
22.668 + state.previndent = state.indent
22.669 + state.lnr = state.lnr + 1
22.670 + end
22.671 +
22.672 + popuntil()
22.673 + doout("exit")
22.674 +end
22.675 +
22.676 +local function _main(s)
22.677 + local t = { }
22.678 + local state = { inbuf = s, genfunc = gen_html,
22.679 + out = function(s) table.insert(t, s) end }
22.680 + parse(state)
22.681 + return table.concat(t), state.is_dynamic_content
22.682 +end
22.683 +
22.684 +-------------------------------------------------------------------------------
22.685 +--
22.686 +-- create module
22.687 +--
22.688 +
22.689 +module "tek.web.markup"
22.690 +
22.691 +VERSION = 0
22.692 +REVISION = 9
22.693 +-------------------------------------------------------------------------------
22.694 +main = _main
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
23.2 +++ b/cgi-bin/weblua.cgi Mon Feb 12 02:14:11 2007 +0100
23.3 @@ -0,0 +1,59 @@
23.4 +#!/usr/local/bin/lua
23.5 +
23.6 +--
23.7 +-- cgi-bin/weblua.cgi - WebLua CGI launcher script
23.8 +-- Written by Timm S. Mueller <tmueller at neoscientists.org>
23.9 +-- See copyright notice in COPYRIGHT
23.10 +--
23.11 +
23.12 +require "tek"
23.13 +require "tek.cgi.document"
23.14 +require "tek.web.include"
23.15 +local cgi = require "tek.cgi"
23.16 +local web = require "tek.web"
23.17 +out = web.out
23.18 +
23.19 +local dh = cgi.document.Handler
23.20 +if dh then
23.21 +
23.22 + -- parse and execute the addressed script
23.23 +
23.24 + local errcode, errtext, errdetail, errtrace = tek.catch(function()
23.25 + local parsed, msg = web.include.load(io.open(dh), "out", dh)
23.26 + if not parsed then
23.27 + tek.throw(1001, "HTML/Lua parsing failed", msg)
23.28 + end
23.29 + parsed()
23.30 + end)
23.31 +
23.32 + if errcode ~= 0 then
23.33 + web.discard()
23.34 + web.setheader("Content-Type: text/html\n\n")
23.35 + out([[
23.36 + <?xml version="1.0"?>
23.37 + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
23.38 + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
23.39 + <head>
23.40 + <meta http-equiv="content-type" content="text/html; charset=ISO-8859-15" />
23.41 + <title>Application error</title>
23.42 + </head>
23.43 + <body>
23.44 + <h2>Application error ]] .. (errcode or "").. [[</h2>
23.45 + <pre>]] .. cgi.encodeform(errtext or "") .. [[</pre>
23.46 + <pre>]] .. cgi.encodeform(errdetail or "") .. [[</pre>
23.47 + <pre>]] .. cgi.encodeform(errtrace or "") .. [[</pre>
23.48 + </body>
23.49 + </html>
23.50 + ]])
23.51 + end
23.52 +
23.53 + -- output buffers
23.54 +
23.55 + web.flush()
23.56 +
23.57 +else
23.58 +
23.59 + out("Content-Type: text/plain\n\n")
23.60 + out("No handler to serve a document at the addressed location")
23.61 +
23.62 +end
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
24.2 +++ b/etc/config.lua Mon Feb 12 02:14:11 2007 +0100
24.3 @@ -0,0 +1,39 @@
24.4 +-------------------------------------------------------------------------------
24.5 +--
24.6 +-- etc/config.lua - Loona Configuration file
24.7 +--
24.8 +-------------------------------------------------------------------------------
24.9 +
24.10 +-- password file --------------------------------------------------------------
24.11 +-- passwdfile = "../etc/passwd.lua";
24.12 +
24.13 +-- session directory ----------------------------------------------------------
24.14 +-- sessiondir = "../var/sessions";
24.15 +
24.16 +-- extensions directory -------------------------------------------------------
24.17 +-- extdir = "../extensions";
24.18 +
24.19 +-- content directory ----------------------------------------------------------
24.20 +-- contentdir = "../content";
24.21 +
24.22 +-- locale directory -----------------------------------------------------------
24.23 +-- localedir = "../locale";
24.24 +
24.25 +-- enable debugging facilities ------------------------------------------------
24.26 +-- debug = true;
24.27 +
24.28 +-- cache rendered html --------------------------------------------------------
24.29 +-- cachehtml = false;
24.30 +
24.31 +-- default/fallback section name ----------------------------------------------
24.32 +-- defname = "home";
24.33 +
24.34 +-- default language -----------------------------------------------------------
24.35 +-- deflang = "de";
24.36 +
24.37 +-- use preferred language hint ------------------------------------------------
24.38 +-- browserlang = false;
24.39 +
24.40 +-- max age of a session [seconds] ---------------------------------------------
24.41 +-- sessionmaxage = 600;
24.42 +
25.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
25.2 +++ b/etc/passwd.lua Mon Feb 12 02:14:11 2007 +0100
25.3 @@ -0,0 +1,3 @@
25.4 +
25.5 + foo = { username = "foo", password = "bar" },
25.6 +
26.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
26.2 +++ b/extensions/checkaccept.lua Mon Feb 12 02:14:11 2007 +0100
26.3 @@ -0,0 +1,75 @@
26.4 +<%
26.5 +
26.6 +os = require "os"
26.7 +web = require "tek.web"
26.8 +cgi = require "tek.cgi"
26.9 +args = require "tek.cgi.request.args"
26.10 +cookies = require "tek.cgi.header.cookies"
26.11 +
26.12 +
26.13 +local cookiename = arg[1]
26.14 +local introtext = arg[2] -- diclaimer
26.15 +local sorrytext = arg[3] -- when declined
26.16 +local regulartext = arg[#arg] -- when first option accepted
26.17 +local accept = args.accept
26.18 +
26.19 +
26.20 +if cookies[cookiename] then
26.21 + local cdata = cgi.decodeurl(cookies[cookiename])
26.22 + accept = accept or cdata:match("(.+)")
26.23 +end
26.24 +
26.25 +if accept then
26.26 + local expdate = string.gsub(os.date("!%c"),
26.27 + "(%w+)%s(%w+)%s(.+)%s(%d+):(%d+):(%d+)%s(%d+)", function(wdy,mon,d,h,m,s,y)
26.28 + return wdy..", "..d.."-"..mon.."-"..(y+1).." "..h..":"..m..":"..s.." GMT"
26.29 + end)
26.30 + cdata = cgi.encodeurl(accept)
26.31 + web.setheader("Set-cookie: "..cookiename.."="..cdata.."; expires="..expdate.."; path=/;\n")
26.32 +end
26.33 +
26.34 +if accept ~= "1" then%>
26.35 +
26.36 + <%if accept then%>
26.37 + <%sorrytext()%>
26.38 + <hr />
26.39 + <%end%>
26.40 +
26.41 + <%introtext()%>
26.42 +
26.43 + <form action="<%=loona.document%>" method="post">
26.44 + <div>
26.45 + <%=loona.hidden("lang", loona.lang)%>
26.46 + <%=loona.hidden("profile", loona.profile)%>
26.47 + <%=loona.hidden("session", loona.session and loona.session.id)%>
26.48 + </div>
26.49 +
26.50 + <table>
26.51 + <%for k, v in ipairs(arg) do
26.52 + if k > 3 and k < #arg then
26.53 + local num = k - 3
26.54 + %>
26.55 + <tr>
26.56 + <td valign="top">
26.57 + <input type="radio" name="accept" value="<%=num%>" />
26.58 + </td>
26.59 + <td valign="top">
26.60 + <%v()%>
26.61 + </td>
26.62 + </tr>
26.63 + <%end
26.64 + end%>
26.65 + <tr>
26.66 + <td colspan="2">
26.67 + <input type="submit" name="submit" value="Weiter..." />
26.68 + </td>
26.69 + </tr>
26.70 + </table>
26.71 + </form>
26.72 +
26.73 +<%else
26.74 +
26.75 + regulartext()
26.76 +
26.77 +end%>
26.78 +
27.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
27.2 +++ b/extensions/contactform.lua Mon Feb 12 02:14:11 2007 +0100
27.3 @@ -0,0 +1,114 @@
27.4 +<%
27.5 +
27.6 +io = require "io"
27.7 +args = require "tek.cgi.request.args"
27.8 +
27.9 +
27.10 +local function purify(s, maxlen)
27.11 + s = s:sub(1, maxlen)
27.12 + -- remove carriage returns and newlines
27.13 + s = s:gsub("[\r\n]", " ")
27.14 + -- remove literal carriage returns and newlines
27.15 + s = s:gsub("\\r", "!r")
27.16 + s = s:gsub("\\n", "!n")
27.17 + -- trim whitespaces
27.18 + s = s:gsub("^%s*(.*)%s*$", "%1")
27.19 + return s
27.20 +end
27.21 +
27.22 +local function sendmail(fromadr, toadr, prefix, name, subject, body)
27.23 + if fromadr and toadr then
27.24 + fromadr = purify(fromadr or "", 60):match("^%S+@%w+[%w._%-]*%.%w+$")
27.25 + toadr = purify(fromadr or "", 60):match("^%S+@%w+[%w._%-]*%.%w+$")
27.26 + if fromadr and toadr and body then
27.27 + name = purify(name or "", 80)
27.28 + subject = purify(subject or "", 120)
27.29 + body = body:sub(1, 2000):gsub("\r", "")
27.30 + prefix = prefix and prefix .. " " or ""
27.31 + local f = io.popen("sendmail -t", "w")
27.32 + if f then
27.33 + f:write("Reply-To: ", fromadr, "\n")
27.34 + f:write("To: ", toadr, "\n")
27.35 + f:write("Subject: ", prefix, subject, "\n\n")
27.36 + f:write(body)
27.37 + return 1 -- success
27.38 + end
27.39 + return -2 -- fail
27.40 + end
27.41 + end
27.42 + return -1 -- error
27.43 +end
27.44 +
27.45 +to = arg[1]
27.46 +prefix = arg[2] or "Contact form"
27.47 +from = args.mailaddress
27.48 +name = args.mailname
27.49 +subject = args.mailsubject
27.50 +body = args.mailbody
27.51 +printsuccessmsg = arg[3] or function()
27.52 + loona.out "Your mail has been sent."
27.53 +end
27.54 +printerrormsg = arg[4] or function()
27.55 + loona.out "Sorry. Some fields were probably left blank."
27.56 +end
27.57 +printfailmsg = arg[5] or function()
27.58 + loona.out "Sorry. An server error or misconfiguration was detected."
27.59 +end
27.60 +
27.61 +assert(to, "Cannot run extension - no destination mail address specified")
27.62 +
27.63 +ret = not args.sendform and 0
27.64 + or sendmail(from, to, prefix, name, subject, body)
27.65 +
27.66 +%>
27.67 +
27.68 +<%if ret == 1 then%>
27.69 +
27.70 + <%printsuccessmsg()%>
27.71 +
27.72 +<%elseif ret == -1 then%>
27.73 +
27.74 + <%printerrormsg()%>
27.75 +
27.76 +<%elseif ret == -2 then%>
27.77 +
27.78 + <%printfailmsg()%>
27.79 +
27.80 +<%else%>
27.81 +
27.82 + <form action="<%=loona.document%>" method="post">
27.83 + <div>
27.84 + <%=loona.hidden("lang", loona.lang)%>
27.85 + <%=loona.hidden("profile", loona.profile)%>
27.86 + <%=loona.hidden("session", loona.session and loona.session.id)%>
27.87 + </div>
27.88 +
27.89 + <table class="editparm">
27.90 + <tr>
27.91 + <td align="right">Name</td>
27.92 + <td><input size="50" maxlength="50" name="mailname" /></td>
27.93 + </tr>
27.94 + <tr>
27.95 + <td align="right">E-Mail</td>
27.96 + <td><input size="50" maxlength="50" name="mailaddress" /></td>
27.97 + </tr>
27.98 + <tr>
27.99 + <td align="right">Betreff</td>
27.100 + <td><input size="70" maxlength="50" name="mailsubject" /></td>
27.101 + </tr>
27.102 + <tr>
27.103 + <td align="right">Text</td>
27.104 + <td>
27.105 + <textarea cols="65" rows="10" name="mailbody"></textarea>
27.106 + </td>
27.107 + </tr>
27.108 + <tr>
27.109 + <td align="right"></td>
27.110 + <td>
27.111 + <input type="submit" name="sendform" value="Absenden" />
27.112 + </td>
27.113 + </tr>
27.114 + </table>
27.115 + </form>
27.116 +
27.117 +<%end%>
28.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
28.2 +++ b/extensions/login.lua Mon Feb 12 02:14:11 2007 +0100
28.3 @@ -0,0 +1,48 @@
28.4 +<%
28.5 +
28.6 +cgi = require "tek.cgi"
28.7 +
28.8 +name = cgi.encodeform(arg[1] or "Name")
28.9 +pass = cgi.encodeform(arg[2] or "Password")
28.10 +loggedas = cgi.encodeform(arg[3] or "You are logged on as user")
28.11 +
28.12 +if loona.authuser then%>
28.13 +
28.14 + <h3><%=loggedas%> <%=loona.authuser%>.</h3>
28.15 + <%=loona.alink(loona.sectionpath, "Logout", "login=false", "session=")%>
28.16 +
28.17 +<%else%>
28.18 +
28.19 + <form action="<%=loona.document%>" method="post" accept-charset="utf-8">
28.20 + <div>
28.21 + <%=loona.hidden("lang", loona.lang)%>
28.22 + </div>
28.23 + <table>
28.24 + <tr>
28.25 + <td>
28.26 + <%=name%> :
28.27 + </td>
28.28 + <td>
28.29 + <input type="text" size="20" maxlength="40" name="login"/>
28.30 + </td>
28.31 + </tr>
28.32 + <tr>
28.33 + <td>
28.34 + <%=pass%> :
28.35 + </td>
28.36 + <td>
28.37 + <input type="password" size="20" maxlength="40" name="password"/>
28.38 + </td>
28.39 + </tr>
28.40 + <tr>
28.41 + <td>
28.42 + </td>
28.43 + <td>
28.44 + <input class="footerform" type="submit" value="Login"/>
28.45 + </td>
28.46 + </tr>
28.47 + </table>
28.48 + </form>
28.49 +
28.50 +<%end%>
28.51 +
29.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
29.2 +++ b/extensions/search.lua Mon Feb 12 02:14:11 2007 +0100
29.3 @@ -0,0 +1,146 @@
29.4 +<%
29.5 +
29.6 +io = require "io"
29.7 +tek = require "tek"
29.8 +cgi = require "tek.cgi"
29.9 +args = require "tek.cgi.request.args"
29.10 +
29.11 +
29.12 +searchlabel = cgi.encodeform(arg[1] or "Search term:")
29.13 +matchlabel = cgi.encodeform(arg[2] or "Hits for search term")
29.14 +destsection = cgi.encodeurl(arg[3] or "search") -- Destination path
29.15 +
29.16 +
29.17 +--
29.18 +-- transformations to normalize text
29.19 +--
29.20 +
29.21 +local transtab = {
29.22 + [228] = "ae", [196] = "Ae",
29.23 + [246] = "oe", [214] = "Oe",
29.24 + [252] = "ue", [220] = "Ue",
29.25 + [223] = "ss"
29.26 +}
29.27 +
29.28 +local function normalize(s)
29.29 + local t = { }
29.30 + for c in tek.utf8values(s) do
29.31 + if transtab[c] then
29.32 + table.insert(t, transtab[c])
29.33 + elseif (c >= 32 and c <= 126) or (c >= 161 and c <= 255) then
29.34 + table.insert(t, string.char(c))
29.35 + else
29.36 + table.insert(t, "?")
29.37 + end
29.38 + end
29.39 + return table.concat(t):lower()
29.40 +end
29.41 +
29.42 +
29.43 +--
29.44 +-- perform fulltext search on files in a directory
29.45 +--
29.46 +
29.47 +local function search(dir, text, filepattern)
29.48 +
29.49 + if dir and text then
29.50 +
29.51 + text = normalize(text)
29.52 + text = text:lower()
29.53 + local matches = {}
29.54 + local f = io.popen('find "' .. dir .. '" -mindepth 1 -maxdepth 1 -type f -printf "%P\n"')
29.55 + if f then
29.56 + for line in f:lines() do
29.57 +
29.58 + -- only find lines that match filepattern
29.59 + if line:match(filepattern) then
29.60 +
29.61 + -- only match if path is valid and visible
29.62 + local tab, idx = loona.checkpath(loona.config.sections, line)
29.63 + if tab and not tab[idx].notvalid then
29.64 +--
29.65 +-- (loona.authuser or not tab[idx].secret) and
29.66 +-- (loona.secure or not tab[idx].secure) then
29.67 + local s = io.open(dir .. "/" .. line)
29.68 + if s then
29.69 + local t = normalize(s)
29.70 + local matchf = 0
29.71 + local numf = 0 -- number of different search terms found
29.72 + text:gsub("(%w+)", function(search)
29.73 + local pos = 0
29.74 + local sum = 0
29.75 + local found
29.76 + repeat
29.77 + pos = t:find(search, pos + 1, true)
29.78 + if pos then
29.79 + sum = sum + ((t:len() - pos) / t:len()) + 1
29.80 + sum = sum + 1
29.81 + found = true
29.82 + end
29.83 + until not pos
29.84 + if found then
29.85 + numf = numf + 1
29.86 + end
29.87 + matchf = matchf + sum --/ t:len()
29.88 + end)
29.89 + if matchf > 0 then
29.90 + matchf = matchf * numf * numf
29.91 + table.insert(matches, { name = line, len = t:len(), factor = matchf })
29.92 + end
29.93 + end
29.94 + end
29.95 +
29.96 + end
29.97 + end
29.98 + table.sort(matches, function(a, b)
29.99 + return a.factor > b.factor
29.100 + end)
29.101 + end
29.102 + return matches
29.103 + end
29.104 +end
29.105 +
29.106 +matches = search(loona.contentdir, args.searchtext, "^[%w_]*$")
29.107 +
29.108 +%>
29.109 +
29.110 +<%if matches then%>
29.111 + <%=#matches%>
29.112 + <%=matchlabel%> <%=cgi.encodeform(args.searchtext)%>
29.113 + <%if #matches > 0 then%>
29.114 + <hr />
29.115 + <table class="searchresults">
29.116 + <%for idx, item in ipairs(matches) do
29.117 + item = item.name:gsub("_", "/")%>
29.118 + <tr>
29.119 + <td>
29.120 + <%=idx%>.
29.121 + </td>
29.122 + <td>
29.123 + <%=loona.alink(item, item)%>
29.124 + </td>
29.125 + </tr>
29.126 + <%end%>
29.127 + </table>
29.128 + <%end%>
29.129 + <hr />
29.130 +<%end%>
29.131 +
29.132 +<table>
29.133 + <tr>
29.134 + <td>
29.135 + <%=searchlabel%>
29.136 + </td>
29.137 + <td>
29.138 + <form action="<%=loona.document%>/<%=destsection%>" method="post" accept-charset="utf-8">
29.139 + <div>
29.140 + <%=loona.hidden("lang", loona.lang)%>
29.141 + <%=loona.hidden("profile", loona.profile)%>
29.142 + <%=loona.hidden("session", loona.session and loona.session.id)%>
29.143 + <input class="searchtext" type="text" size="40" maxlength="40"
29.144 + name="searchtext" value="<%=cgi.encodeform(args.searchtext or "")%>" />
29.145 + </div>
29.146 + </form>
29.147 + </td>
29.148 + </tr>
29.149 +</table>
30.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
30.2 +++ b/extensions/titlesplash.lua Mon Feb 12 02:14:11 2007 +0100
30.3 @@ -0,0 +1,21 @@
30.4 +
30.5 +<table width="100%" style="border: none; height: 100%;">
30.6 + <%for y = 0, 1 do%>
30.7 + <tr>
30.8 + <%for x = 0, 1 do%>
30.9 + <td width="50%" height="50%" style="padding: 10px;">
30.10 + <table style="height: 100%; width: 100%;">
30.11 + <tr>
30.12 + <td style="border-left: 16px solid #F66014;">
30.13 + </td>
30.14 + <td class="title" style="border: 1px solid #F66014;
30.15 + width: 100%; padding: 16px;">
30.16 + <%arg[y * 2 + x + 1]()%>
30.17 + </td>
30.18 + </tr>
30.19 + </table>
30.20 + </td>
30.21 + <%end%>
30.22 + </tr>
30.23 + <%end%>
30.24 +</table>
31.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
31.2 +++ b/htdocs/.htaccess Mon Feb 12 02:14:11 2007 +0100
31.3 @@ -0,0 +1,5 @@
31.4 +Options -Indexes
31.5 +AddHandler lua-script .lua
31.6 +Action lua-script /cgi-bin/weblua.cgi
31.7 +DirectoryIndex index.html index.lua
31.8 +
32.1 Binary file htdocs/favicon.ico has changed
33.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
33.2 +++ b/htdocs/index.lua Mon Feb 12 02:14:11 2007 +0100
33.3 @@ -0,0 +1,110 @@
33.4 +<%
33.5 +
33.6 +require "loona"
33.7 +
33.8 +function MLINK(section, text, ...)
33.9 + return '<a class="menu" href="' .. loona.href(section, unpack(arg)) ..
33.10 + '">' .. text .. '</a>'
33.11 +end
33.12 +
33.13 +function MALINK(section, text, ...)
33.14 + return '<a class="activemenu" href="' ..
33.15 + loona.href(section, unpack(arg)) .. '">' .. text .. '</a>'
33.16 +end
33.17 +
33.18 +function MENU(menupath, level)
33.19 + if loona.submenus[level] then
33.20 + local entries = loona.submenus[level].entries
33.21 + if entries then
33.22 + local activename = loona.submenus[level].name
33.23 + local numvis = 0
33.24 + for _, val in ipairs(entries) do
33.25 + if not val.notvisible then
33.26 + numvis = numvis + 1
33.27 + end
33.28 + end
33.29 + -- do not enter recursion if no entries are visible
33.30 + if numvis > 0 then%>
33.31 + <ul id="loonaMenu<%=level%>">
33.32 + <%for _, val in ipairs(entries) do
33.33 + if not val.notvisible then
33.34 + local curpath = (menupath and menupath .. "/" .. val.name) or val.name
33.35 + local label = (val.label ~= "" and val.label) or val.name%>
33.36 + <li>
33.37 + <%if activename == val.name then%>
33.38 + <%=MALINK(curpath, label, val.action)%>
33.39 + <%if val.subs or loona.authuser then%>
33.40 + <%MENU(curpath, level + 1)%>
33.41 + <%end%>
33.42 + <%else%>
33.43 + <%=MLINK(curpath, label, val.action)%>
33.44 + <%end%>
33.45 + </li>
33.46 + <%end
33.47 + end%>
33.48 + </ul>
33.49 + <%end
33.50 + end
33.51 + end
33.52 +end
33.53 +
33.54 +-------------------------------------------------------------------------------
33.55 +
33.56 +loona.setheader "Content-Type: text/html\n\n"
33.57 +%>
33.58 +<?xml version="1.0"?>
33.59 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
33.60 +
33.61 +<html xmlns="http://www.w3.org/1999/xhtml" lang="<%=loona.lang%>">
33.62 +
33.63 +<head>
33.64 + <link rel="stylesheet" type="text/css" href='/loona.css' />
33.65 + <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
33.66 + <title>
33.67 + Loona CMS
33.68 + <%=loona.section and (": " .. (loona.section.title or loona.section.label or loona.section.name))%>
33.69 + </title>
33.70 +</head>
33.71 +
33.72 +<body>
33.73 +
33.74 + <div id="container">
33.75 +
33.76 + <div id="menu">
33.77 + <div id="loonaMenu">
33.78 + <%MENU(nil, 1)%>
33.79 + </div>
33.80 + </div>
33.81 +
33.82 + <div id="top">
33.83 + <div id="loonaTop">
33.84 + <%loona.body("top")%>
33.85 + </div>
33.86 + </div>
33.87 +
33.88 + <div id="main">
33.89 + <div id="loonaMain">
33.90 + <%loona.body("main")%>
33.91 + </div>
33.92 + </div>
33.93 +
33.94 + <div id="side">
33.95 + <div id="loonaSide">
33.96 + <%loona.body("side")%>
33.97 + </div>
33.98 + </div>
33.99 +
33.100 + <%if loona.config.debug == true then%>
33.101 + <div id="debug">
33.102 + <%=os.clock()%><br />
33.103 + <a href="http://validator.w3.org/check?uri=referer"><img
33.104 + src="http://www.w3.org/Icons/valid-xhtml10"
33.105 + alt="Valid XHTML 1.0 Strict" height="31" width="88" /></a>
33.106 + </div>
33.107 + <%end%>
33.108 +
33.109 + </div>
33.110 +
33.111 +</body>
33.112 +
33.113 +</html>
34.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
34.2 +++ b/htdocs/loona.css Mon Feb 12 02:14:11 2007 +0100
34.3 @@ -0,0 +1,109 @@
34.4 +
34.5 +html {
34.6 + margin: 0px;
34.7 + padding: 0px;
34.8 +}
34.9 +
34.10 +body {
34.11 + background: #000;
34.12 + color: #ccc;
34.13 + margin: 0px;
34.14 + padding: 0px;
34.15 + font: 11pt/18pt verdana, sans;
34.16 +}
34.17 +
34.18 +.warning {
34.19 + background-color:#b00;
34.20 + color:#ff0;
34.21 + padding: 0.2em;
34.22 +}
34.23 +
34.24 +/*****************************************************************************/
34.25 +
34.26 +
34.27 +#container {
34.28 + border: 1px solid cyan;
34.29 + padding: 180px 150px 0px 200px;
34.30 +}
34.31 +
34.32 +#menu {
34.33 + padding: 0px;
34.34 + margin: 0px;
34.35 + border: 1px solid magenta;
34.36 + position: absolute;
34.37 + top: 0px;
34.38 + left: 0px;
34.39 + max-width: 200px;
34.40 +}
34.41 +
34.42 +#top {
34.43 + padding: 0px;
34.44 + margin: 0px;
34.45 + border: 1px solid magenta;
34.46 + position: absolute;
34.47 + top: 0px;
34.48 + left: 200px;
34.49 +}
34.50 +
34.51 +#main {
34.52 + padding: 0px;
34.53 + margin: 0px;
34.54 + border: 1px solid magenta;
34.55 +}
34.56 +
34.57 +#side {
34.58 + padding: 0px;
34.59 + margin: 0px;
34.60 + border: 1px solid green;
34.61 + position: absolute;
34.62 + top: 0px;
34.63 + right: 0px;
34.64 + width: 150px;
34.65 +}
34.66 +
34.67 +#debug {
34.68 + padding: 0px;
34.69 + margin: 0px;
34.70 + border: 1px solid magenta;
34.71 +}
34.72 +
34.73 +
34.74 +/*****************************************************************************/
34.75 +
34.76 +
34.77 +#loonaMenu {
34.78 + padding: 10px 10px 0px 0px;
34.79 + font: 10pt/16pt verdana, sans-serif;
34.80 +}
34.81 +
34.82 +#loonaMenu ul {
34.83 + margin: 0px;
34.84 + padding: 0px;
34.85 + margin-left: 10px;
34.86 +}
34.87 +
34.88 +#loonaMenu li {
34.89 + list-style-type: none;
34.90 +}
34.91 +
34.92 +
34.93 +/*****************************************************************************/
34.94 +
34.95 +
34.96 +#loonaMain .indent {
34.97 + margin-left: 1em;
34.98 +}
34.99 +
34.100 +#loonaMain ul {
34.101 + list-style-position: outside;
34.102 + margin: 0px;
34.103 + padding: 0px;
34.104 +/* border: 1px solid #666; */
34.105 +}
34.106 +
34.107 +#loonaMain div {
34.108 + margin: 0px;
34.109 + padding: 0px;
34.110 +/* border: 1px solid #666; */
34.111 +}
34.112 +
35.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
35.2 +++ b/htdocs/upload.lua Mon Feb 12 02:14:11 2007 +0100
35.3 @@ -0,0 +1,45 @@
35.4 +<%
35.5 +require "loona"
35.6 +
35.7 +if tek.cgi.request.args.file and tek.cgi.request.args.show then
35.8 +
35.9 + local f = tek.cgi.request.args.file.file:read("*a")
35.10 + io.stdout:write("Content-Type: " .. tek.cgi.request.args.file["content-type"] .. "\n\n")
35.11 + io.stdout:write(f)
35.12 +
35.13 +else
35.14 +
35.15 + tek.web.setheader "Content-Type: text/html\n\n"
35.16 +%>
35.17 + <?xml version="1.0"?>
35.18 + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
35.19 + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
35.20 + <head>
35.21 + <title>
35.22 + Upload Test
35.23 + </title>
35.24 + </head>
35.25 + <body>
35.26 + <p>Bitte Bild hochladen!</p>
35.27 + <form action="<%=tek.cgi.document.Name%>" method="post" enctype="multipart/form-data">
35.28 + <input type="hidden" name="processupload" value="true" />
35.29 + <br />
35.30 + <input name="file" type="file" size="50" maxlength="100000" accept="image/*" />
35.31 + <br />
35.32 + Bild anzeigen: <input type="checkbox" name="show" />
35.33 + <br />
35.34 + <input type="submit" />
35.35 + </form>
35.36 + <hr />
35.37 +<pre>
35.38 +<%tek.dump(tek.cgi.request, tek.web.out)%>
35.39 +</pre>
35.40 + <hr />
35.41 +<pre>
35.42 +<%tek.dump(tek.cgi.document, tek.web.out)%>
35.43 +</pre>
35.44 + </body>
35.45 + </html>
35.46 +
35.47 +<%end%>
35.48 +
36.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
36.2 +++ b/locale/de Mon Feb 12 02:14:11 2007 +0100
36.3 @@ -0,0 +1,48 @@
36.4 +NEW = "Neu",
36.5 +CREATE = "Anlegen",
36.6 +EDIT = "Bearbeiten",
36.7 +DELETE = "Löschen",
36.8 +CHANGE = "Wechseln",
36.9 +PUBLISH = "Publizieren",
36.10 +OVERWRITE = "Überschreiben",
36.11 +PROPERTIES = "Eigenschaften",
36.12 +PROFILE = "Profil",
36.13 +CHANGEPROFILE = "In Profil wechseln",
36.14 +CREATEPROFILE = "Aktuelles Profil kopieren nach",
36.15 +PUBLISHPROFILE = "Aktuelles Profil publizieren",
36.16 +DELETEPROFILE = "Aktuelles Profil löschen",
36.17 +SAVE = "Speichern",
36.18 +CANCEL = "Abbrechen",
36.19 +PREVIEW = "Vorschau",
36.20 +MOVEUP = "Rauf",
36.21 +MOVEDOWN = "Runter",
36.22 +ERROR = "Fehler",
36.23 +PATHNAME = "Pfadname",
36.24 +MENULABEL = "Menu-Label",
36.25 +WINDOWTITLE = "Fenstertitel",
36.26 +INVISIBLE = "Nicht im Menü",
36.27 +SECRET = "Nur nach Anmeldung",
36.28 +SECURE_CONNECTION = "Nur via HTTPS",
36.29 +REDIRECT = "Umleiten nach",
36.30 +FILENAME_IS = "Dateiname : ",
36.31 +CANNOT_SAVE_SECTION = "Kann Sektion nicht sichern",
36.32 +INVALID_NAME = "Ungültiger Name oder ungültige Zeichen im Namen",
36.33 +CREATE_NEW_SECTION_UNDER = "Neue Sektion anlegen unterhalb von",
36.34 +PUBLIC = "PUBLIK",
36.35 +WARNING_YOU_ARE_IN_PUBLIC_PROFILE = "Warnung: Sie sind im veröffentlichten Profil",
36.36 +ALERT_OVERWRITE_EXISTING_PROFILE = "Unter diesem Namen existiert bereits ein Profil. Wollen Sie es überschreiben?",
36.37 +ALERT_OVERWRITE_PUBLISHED_PROFILE = "Dies ist das publizierte Profil. Wollen Sie es überschreiben?",
36.38 +ALERT_DELETE_PROFILE = "Sind Sie sicher, dass Sie dieses Profil löschen möchten?",
36.39 +CANNOT_DELETE_PUBLISHED_PROFILE = "Es wurde versucht, das veröffentlichte Profil zu löschen",
36.40 +ALERT_PUBLISH_PROFILE = "Sind Sie sicher, dass Sie dieses Profil veröffentlichen möchten?",
36.41 +CHANGED = "Geändert",
36.42 +ALERT_CANNOT_COPY_PROFILE_TO_SELF = "Ein Profil kann nicht auf sich selbst kopiert werden.",
36.43 +MENU_CURRENTLY_IN_USE = "Auf die Menüstruktur kann nicht zugegriffen werden - sie existiert nicht oder wird gerade bearbeitet.",
36.44 +SECTION_IN_USE = "Die Sektion wird gerade von jemand anderem bearbeitet. Bitte versuchen Sie es später noch einmal!",
36.45 +SECTION_COULD_HAVE_CHANGED = "Warnung: Sie riskieren, von jemand anderem gemachte Änderungen zu überschreiben.",
36.46 +CREATOR = "Ersteller",
36.47 +CREATIONDATE = "Erstellungsdatum",
36.48 +LASTREVISIONER = "Letzte Änderung durch",
36.49 +LASTREVISIONDATE = "Datum der letzten Änderung",
36.50 +ALERT_DELETE_NODE = "Sind Sie sicher, dass Sie diese Sektion löschen wollen?",
36.51 +SECTION_IS_REDIRECT = "Diese Sektion leitet den Benutzer weiter nach",
37.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
37.2 +++ b/locale/en Mon Feb 12 02:14:11 2007 +0100
37.3 @@ -0,0 +1,48 @@
37.4 +NEW = "New",
37.5 +CREATE = "Create",
37.6 +EDIT = "Edit",
37.7 +DELETE = "Delete",
37.8 +CHANGE = "Change",
37.9 +PUBLISH = "Publish",
37.10 +OVERWRITE = "Overwrite",
37.11 +PROPERTIES = "Properties",
37.12 +PROFILE = "Profile",
37.13 +CHANGEPROFILE = "Change to profile",
37.14 +CREATEPROFILE = "Copy current profile to",
37.15 +PUBLISHPROFILE = "Publish current profile",
37.16 +DELETEPROFILE = "Delete current profile",
37.17 +SAVE = "Save",
37.18 +CANCEL = "Cancel",
37.19 +PREVIEW = "Preview",
37.20 +MOVEUP = "Up",
37.21 +MOVEDOWN = "Down",
37.22 +ERROR = "Error",
37.23 +PATHNAME = "Pathname",
37.24 +MENULABEL = "Menu label",
37.25 +WINDOWTITLE = "Window title",
37.26 +INVISIBLE = "Hide in menu",
37.27 +SECRET = "Only when logged on",
37.28 +SECURE_CONNECTION = "Only via HTTPS",
37.29 +REDIRECT = "Redirect to",
37.30 +FILENAME_IS = "Filename : ",
37.31 +CANNOT_SAVE_SECTION = "Can't save section",
37.32 +INVALID_NAME = "Invalid name or invalid characters in name",
37.33 +CREATE_NEW_SECTION_UNDER = "Create new section under",
37.34 +PUBLIC = "PUBLIC",
37.35 +WARNING_YOU_ARE_IN_PUBLIC_PROFILE = "Warning: You are in the public profile",
37.36 +ALERT_OVERWRITE_EXISTING_PROFILE = "A profile of this name already exists. Do you want to overwrite it?",
37.37 +ALERT_OVERWRITE_PUBLISHED_PROFILE = "This is the published profile. Do you want to overwrite it?",
37.38 +ALERT_DELETE_PROFILE = "Are you sure you want to delete this profile?",
37.39 +CANNOT_DELETE_PUBLISHED_PROFILE = "It was attempted to delete the published profile",
37.40 +ALERT_PUBLISH_PROFILE = "Are you sure you want to publish this profile?",
37.41 +CHANGED = "Changed",
37.42 +ALERT_CANNOT_COPY_PROFILE_TO_SELF = "Cannot copy a profile over itself.",
37.43 +MENU_CURRENTLY_IN_USE = "Can't access menu structure - it doesn't exist or is being modified by somebody else.",
37.44 +SECTION_IN_USE = "This section is currently being edited by somebody else. Please try again later!",
37.45 +SECTION_COULD_HAVE_CHANGED = "Warning: You risk to overwrite changes that could have been made by somebody else.",
37.46 +CREATOR = "Creator",
37.47 +CREATIONDATE = "Creation date",
37.48 +LASTREVISIONER = "Last revisioner",
37.49 +LASTREVISIONDATE = "Last revisioned",
37.50 +ALERT_DELETE_NODE = "Are you sure you want to delete this text node?",
37.51 +SECTION_IS_REDIRECT = "This section redirects the user to",