initial import initial
authortmueller@vstst.herrje.net
Mon, 12 Feb 2007 02:14:11 +0100
changeset 0d4b36cfa50e8
child 1 f981a60052c8
initial import
.hgignore
COPYRIGHT
Makefile
cgi-bin/Makefile
cgi-bin/cgilua/post.lua
cgi-bin/cgilua/readuntil.lua
cgi-bin/cgilua/urlcode.lua
cgi-bin/loona.lua
cgi-bin/loona/editable.lua
cgi-bin/tek.lua
cgi-bin/tek/cgi.lua
cgi-bin/tek/cgi/document.lua
cgi-bin/tek/cgi/header.lua
cgi-bin/tek/cgi/header/cookies.lua
cgi-bin/tek/cgi/request.lua
cgi-bin/tek/cgi/request/args.lua
cgi-bin/tek/cgi/session.lua
cgi-bin/tek/posix.c
cgi-bin/tek/util.lua
cgi-bin/tek/web.lua
cgi-bin/tek/web/include.c
cgi-bin/tek/web/markup.lua
cgi-bin/weblua.cgi
etc/config.lua
etc/passwd.lua
extensions/checkaccept.lua
extensions/contactform.lua
extensions/login.lua
extensions/search.lua
extensions/titlesplash.lua
htdocs/.htaccess
htdocs/favicon.ico
htdocs/index.lua
htdocs/loona.css
htdocs/upload.lua
locale/de
locale/en
     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", "&nbsp;")
   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("&nbsp;", "\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("&nbsp;", "\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("&nbsp;", "\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, "&quot;")
   11.33 +		elseif c == 38 then
   11.34 +			insert(tab, "&amp;")
   11.35 +		elseif c == 60 then
   11.36 +			insert(tab, "&lt;")
   11.37 +		elseif c == 62 then
   11.38 +			insert(tab, "&gt;")
   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, "&amp;")
   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 : &lt;/" .. (tag or "nil") .. "&gt;, found : &lt;/" .. (curtag.tag or "nil") .. "&gt;"
  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%> :&nbsp;
   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%> :&nbsp;
   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%>&nbsp;
  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",