Editing and re-publishing is now supported in public profile (configurable via
authorTimm S. Mueller <tmueller@neoscientists.org>
Fri, 05 Oct 2007 01:41:59 +0200
changeset 19887a4de7c7457
parent 196 5e30e5c089ad
child 199 8b5fc485edf4
Editing and re-publishing is now supported in public profile (configurable via
new option); checkpath() fixed, did not properly take into account the full
path; isdynamic() takes into account existance of the addressed link now; if a
link target does not exist, it is no longer referenced as static HTML in an
unrolled site (allows unknown link targets to be processed by other webserver
handlers); an userdata argument can now be passed to LOona; variables fixed
that were misplaced in global namespace; tek.os.posix.abspath() now handles
paths that were absolute already; added fastcgi external server launcher;
inheritance is now based on tek.class.atom; added lua.cgi cgi wrapper; added
fine-grained permissive rights; request, document and post classes overhauled;
underscore now permitted in cgi arguments; quotation marks now handled in
serialization; added checkpw method that can be used as a login hook; improved
buffer logic, setheader renamed to addheader; site template is more flexible
and uses config.defname as the logo's link target now
Makefile
cgi-bin/Makefile
cgi-bin/loona_fastcgi.lua
cgi-bin/lua.cgi
cgi-bin/tek/app/loona.lua
cgi-bin/tek/class/cgi/post.lua
cgi-bin/tek/class/cgi/request.lua
cgi-bin/tek/class/loona.lua
cgi-bin/tek/class/loona/buffer.lua
cgi-bin/tek/class/loona/markup.lua
cgi-bin/tek/lib.lua
cgi-bin/tek/os/posix.c
etc/config.lua.sample
etc/passwd.lua.sample
extensions/checkaccept.lua
htdocs/index.lua
htdocs/loona.css
htdocs/upload.lua
     1.1 --- a/Makefile	Mon May 28 11:24:23 2007 +0200
     1.2 +++ b/Makefile	Fri Oct 05 01:41:59 2007 +0200
     1.3 @@ -3,7 +3,7 @@
     1.4  #	Loona Makefile
     1.5  #	Written by Timm S. Mueller <tmueller at neoscientists.org>
     1.6  #	See copyright notice in COPYRIGHT
     1.7 -#	
     1.8 +#
     1.9  
    1.10  ###############################################################################
    1.11  
    1.12 @@ -40,13 +40,15 @@
    1.13  	@echo
    1.14  	@echo "all ........... All of the above: modules, setup, permissions"
    1.15  	@echo
    1.16 +	@echo "fastcgi-run ... Run as FastCGI external server"
    1.17 +	@echo
    1.18  	@echo "NOTE .......... Set environment variables to complement your setup, e.g."
    1.19  	@echo "                % LANGUAGE=de WWWUSER=wwwrun make help"
    1.20  	@echo "                or make settings persistent by changing them in the Makefile"
    1.21  	@echo
    1.22  
    1.23  
    1.24 -clean install modules: 
    1.25 +clean install modules:
    1.26  	$(MAKE) -C cgi-bin $@
    1.27  
    1.28  
    1.29 @@ -77,8 +79,12 @@
    1.30  all: modules setup permissions
    1.31  
    1.32  
    1.33 -distclean: 
    1.34 +distclean:
    1.35  	-rm -Rf $(CONTENTDIR)
    1.36  	-rm -Rf $(VARDIR)
    1.37  	-rm cgi-bin/tek/*.o cgi-bin/tek/*.so cgi-bin/tek/web/*.o cgi-bin/tek/web/*.so
    1.38  	-rm -f $(HTDIR)/*.html $(HTDIR)/*.html.*
    1.39 +
    1.40 +
    1.41 +fastcgi-run:
    1.42 +	$(MAKE) -C cgi-bin $@
     2.1 --- a/cgi-bin/Makefile	Mon May 28 11:24:23 2007 +0200
     2.2 +++ b/cgi-bin/Makefile	Fri Oct 05 01:41:59 2007 +0200
     2.3 @@ -14,6 +14,8 @@
     2.4  	@echo "modules ....... Build modules"
     2.5  	@echo "install ....... Install modules [INSTPATH: $(INSTPATH)]"
     2.6  	@echo
     2.7 +	@echo "fastcgi-run ... Run as FastCGI external server [127.0.0.1:20000]"
     2.8 +	@echo
     2.9  	@echo "NOTE .......... Modules can reside locally under cgi-bin."
    2.10  	@echo "                An installation is not strictly required."
    2.11  	@echo
    2.12 @@ -33,6 +35,8 @@
    2.13  	-luac -s -o $(INSTPATH)/tek/lib.lua tek/lib.lua
    2.14  	-luac -s -o $(INSTPATH)/tek/app/loona.lua tek/app/loona.lua
    2.15  	-luac -s -o $(INSTPATH)/tek/class/cgi.lua tek/class/cgi.lua
    2.16 +	-luac -s -o $(INSTPATH)/tek/class/atom.lua tek/class/atom.lua
    2.17 +	-luac -s -o $(INSTPATH)/tek/class/fastcgi.lua tek/class/fastcgi.lua
    2.18  	-luac -s -o $(INSTPATH)/tek/class/cgi/post.lua tek/class/cgi/post.lua
    2.19  	-luac -s -o $(INSTPATH)/tek/class/cgi/request.lua tek/class/cgi/request.lua
    2.20  	-luac -s -o $(INSTPATH)/tek/class/loona.lua tek/class/loona.lua
    2.21 @@ -48,3 +52,6 @@
    2.22  
    2.23  clean:
    2.24  	-rm tek/lib/*.so tek/lib/*.o tek/os/*.so tek/os/*.o
    2.25 +
    2.26 +fastcgi-run:
    2.27 +	./loona_fastcgi.lua
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/cgi-bin/loona_fastcgi.lua	Fri Oct 05 01:41:59 2007 +0200
     3.3 @@ -0,0 +1,135 @@
     3.4 +#!/usr/bin/env lua
     3.5 +
     3.6 +--
     3.7 +--	LOona Tiny CMS - FastCGI external server
     3.8 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
     3.9 +--	See copyright notice in COPYRIGHT
    3.10 +--
    3.11 +
    3.12 +local db = require "tek.lib.debug"
    3.13 +local socket = require "socket"
    3.14 +local loona = require "tek.app.loona"
    3.15 +local FCGI = require "tek.class.fastcgi"
    3.16 +local Buffer = require "tek.class.loona.buffer"
    3.17 +local Request = require "tek.class.cgi.request"
    3.18 +
    3.19 +-------------------------------------------------------------------------------
    3.20 +--	constants and globals:
    3.21 +-------------------------------------------------------------------------------
    3.22 +
    3.23 +-- set global debug level:
    3.24 +db.level = 4
    3.25 +
    3.26 +-- The script name we enforce:
    3.27 +local SCRIPTNAME = "/index.lua"
    3.28 +
    3.29 +-- HTTP POST limits:
    3.30 +local MAX_CONTENT_LENGTH = 1048575
    3.31 +local MAX_FILESIZE = 524288
    3.32 +
    3.33 +-------------------------------------------------------------------------------
    3.34 +--	FCGI Buffer class:
    3.35 +-------------------------------------------------------------------------------
    3.36 +
    3.37 +local FCGIBuffer = Buffer:newclass()
    3.38 +
    3.39 +function FCGIBuffer:flush()
    3.40 +	local fcgi = self.fcgi
    3.41 +	local req_id = self.req_id
    3.42 +	for _, line in ipairs(self.headerbuf) do
    3.43 +		fcgi:write_stdout(req_id, line)
    3.44 +	end
    3.45 +	for _, line in ipairs(self.outbuf) do
    3.46 +		fcgi:write_stdout(req_id, line)
    3.47 +	end
    3.48 +	fcgi:write_stdout(req_id, "") -- finish
    3.49 +end
    3.50 +
    3.51 +-------------------------------------------------------------------------------
    3.52 +--	FCGI Server class:
    3.53 +-------------------------------------------------------------------------------
    3.54 +
    3.55 +local FCGIServer = FCGI:newclass()
    3.56 +
    3.57 +function FCGIServer:runapp(req)
    3.58 +	loona.main(self.buffer, self.request, self.document, self.userdata)
    3.59 +	self:endrequest(req) -- confirm
    3.60 +end
    3.61 +
    3.62 +function FCGIServer:abortrequest(req)
    3.63 +	self:endrequest(req) -- confirm
    3.64 +end
    3.65 +
    3.66 +function FCGIServer:have_stream(req, type, s)
    3.67 +	self.request.read = function(n)
    3.68 +		return s:read(n)
    3.69 +	end
    3.70 +	return self:runapp(req)
    3.71 +end
    3.72 +
    3.73 +function FCGIServer:have_params(req, params)
    3.74 +
    3.75 +	self.request = Request:new {
    3.76 +		maxinput = MAX_CONTENT_LENGTH,
    3.77 +		maxfilesize = MAX_FILESIZE,
    3.78 +		getenv = function(name)
    3.79 +			return params[name]
    3.80 +		end,
    3.81 +	}
    3.82 +
    3.83 +	local script, vpath = self.request.REQUEST_URI:match("^(/[^/]*)([^?]*)")
    3.84 +
    3.85 +	-- enforce scriptname:
    3.86 +	script = SCRIPTNAME
    3.87 +	self.request.REQUEST_URI = script .. vpath
    3.88 +
    3.89 +	local pat = script:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1")
    3.90 +	local requestpath = self.request.SCRIPT_FILENAME:match("(.*)" .. pat .. "$")
    3.91 +		or self.request.SCRIPT_FILENAME
    3.92 +	requestpath = requestpath .. script .. vpath
    3.93 +
    3.94 +	self.document = self.request:getdocument(requestpath)
    3.95 +
    3.96 +	self.buffer = FCGIBuffer:new { fcgi = self, req_id = req.id }
    3.97 +
    3.98 +	if self.request.REQUEST_METHOD == "GET" then
    3.99 +		self:runapp(req)
   3.100 +	elseif self.request.REQUEST_METHOD == "POST" then
   3.101 +		if self.request.CONTENT_LENGTH <= MAX_CONTENT_LENGTH then
   3.102 +			return true
   3.103 +		end
   3.104 +		-- else we do not accept the stream
   3.105 +		db.warn("FastCGI stream not accepted due to POST limit")
   3.106 +	end
   3.107 +
   3.108 +	return false
   3.109 +end
   3.110 +
   3.111 +-------------------------------------------------------------------------------
   3.112 +--	main:
   3.113 +-------------------------------------------------------------------------------
   3.114 +
   3.115 +-- global application state:
   3.116 +local app = { }
   3.117 +
   3.118 +-- create FastCGI listening socket:
   3.119 +app.server = socket.tcp()
   3.120 +app.server:setoption("reuseaddr", true)
   3.121 +app.server:bind("127.0.0.1", 20000)
   3.122 +app.server:listen(64)
   3.123 +
   3.124 +-- run:
   3.125 +while true do
   3.126 +	local client, msg = app.server:accept()
   3.127 +	if client then
   3.128 +		local t0 = socket.gettime()
   3.129 +		local mem0 = collectgarbage("count")
   3.130 +		FCGIServer:new { userdata = app }:serve(client)
   3.131 +		client:close()
   3.132 +		local mem1 = collectgarbage("count")
   3.133 +		collectgarbage("collect")
   3.134 +		local t = socket.gettime() - t0
   3.135 +		db.info("Mem used: %.1fk - for request: %.1fk - elapsed: %.2fs",
   3.136 +			mem1, mem1 - mem0, t)
   3.137 +	end
   3.138 +end
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/cgi-bin/lua.cgi	Fri Oct 05 01:41:59 2007 +0200
     4.3 @@ -0,0 +1,5 @@
     4.4 +#!/usr/local/bin/lua
     4.5 +--
     4.6 +--	Mini CGI wrapper
     4.7 +--
     4.8 +dofile(os.getenv("PATH_TRANSLATED"))
     5.1 --- a/cgi-bin/tek/app/loona.lua	Mon May 28 11:24:23 2007 +0200
     5.2 +++ b/cgi-bin/tek/app/loona.lua	Fri Oct 05 01:41:59 2007 +0200
     5.3 @@ -15,23 +15,29 @@
     5.4  module "tek.app.loona"
     5.5  
     5.6  
     5.7 -_VERSION = 1
     5.8 -_REVISION = 1
     5.9 +_VERSION = 2
    5.10 +_REVISION = 0
    5.11  
    5.12  
    5.13 -function main()
    5.14 +function main(buf, request, document, userdata)
    5.15  	local res, msg = -1, "No handler to serve the addressed document"
    5.16 -	local document = Request:new():getdocument()
    5.17 -	local buf = Buffer:new()
    5.18 +
    5.19 +	buf = buf or Buffer:new()
    5.20 +	request = request or Request:new()
    5.21 +	document = document or request:getdocument()
    5.22 +
    5.23  	if document.Handler then
    5.24  		local loona, detail, trace
    5.25  		res, msg, detail, trace = lib.catch(function()
    5.26 -			loona = Loona:new { buf = buf }
    5.27 +			-- create and run Loona instance:
    5.28 +			loona = Loona:new { buf = buf, request = request,
    5.29 +				document = document, userdata = userdata }
    5.30  			loona:run()
    5.31 +
    5.32  		end)
    5.33  		if res ~= 0 then
    5.34  			buf:discard()
    5.35 -			buf:setheader "Content-Type: text/html; charset=utf-8\n\n"
    5.36 +			buf:addheader "Content-Type: text/html; charset=utf-8\n\n"
    5.37  			buf:out([[
    5.38  <?xml version="1.0"?>
    5.39  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    5.40 @@ -56,7 +62,7 @@
    5.41  ]])
    5.42  		end
    5.43  	else
    5.44 -		buf:setheader "Content-Type: text/html; charset=utf-8\n\n"
    5.45 +		buf:addheader "Content-Type: text/html; charset=utf-8\n\n"
    5.46  		buf:out(msg)
    5.47  	end
    5.48  	buf:flush()
     6.1 --- a/cgi-bin/tek/class/cgi/post.lua	Mon May 28 11:24:23 2007 +0200
     6.2 +++ b/cgi-bin/tek/class/cgi/post.lua	Fri Oct 05 01:41:59 2007 +0200
     6.3 @@ -6,6 +6,7 @@
     6.4  --
     6.5  
     6.6  
     6.7 +local Atom = require "tek.class.atom"
     6.8  local getfenv, setmetatable = getfenv, setmetatable
     6.9  local char = string.char
    6.10  local assert, error, tonumber, type = assert, error, tonumber, type
    6.11 @@ -18,7 +19,7 @@
    6.12  
    6.13  
    6.14  _VERSION = 1
    6.15 -_REVISION = 0
    6.16 +_REVISION = 1
    6.17  
    6.18  
    6.19  -------------------------------------------------------------------------------
    6.20 @@ -110,16 +111,9 @@
    6.21  --	POST parser
    6.22  
    6.23  
    6.24 -local Post = getfenv()
    6.25 +local Post = Atom:newclass(getfenv())
    6.26  
    6.27  
    6.28 -function Post:new(o)
    6.29 -	o = o or { }
    6.30 -	setmetatable(o, self)
    6.31 -	self.__index = self
    6.32 -	return o
    6.33 -end
    6.34 -
    6.35  -------------------------------------------------------------------------------
    6.36  -- Read the headers of the next multipart/form-data field
    6.37  --
    6.38 @@ -262,7 +256,7 @@
    6.39  	self.maxfilesize = self.maxfilesize or inputsize
    6.40  	local _, _, boundary = self.content_type:find("boundary%=(.-)$")
    6.41  	self.boundary = "--" .. boundary
    6.42 -	
    6.43 +
    6.44  	while true do
    6.45  		-- read the next field header(s)
    6.46  		local headers = self:readfieldheaders()
    6.47 @@ -297,7 +291,7 @@
    6.48  	assert (type(defs.args) == "table", "field `args' must be a table")
    6.49  	assert (defs.read)
    6.50  	assert (defs.content_type)
    6.51 -	
    6.52 +
    6.53  	self.read = defs.read
    6.54  	self.readuntil = iterate (function ()
    6.55  		if self.bytesleft <= 0 then return nil end
    6.56 @@ -319,7 +313,7 @@
    6.57  	if defs.maxfilesize then
    6.58  		self.maxfilesize = defs.maxfilesize
    6.59  	end
    6.60 -	
    6.61 +
    6.62  	-- get the "total" size of the incoming data
    6.63  	local inputsize = tonumber(defs.content_length) or 0
    6.64  	if inputsize > self.maxinput then
     7.1 --- a/cgi-bin/tek/class/cgi/request.lua	Mon May 28 11:24:23 2007 +0200
     7.2 +++ b/cgi-bin/tek/class/cgi/request.lua	Fri Oct 05 01:41:59 2007 +0200
     7.3 @@ -8,73 +8,68 @@
     7.4  --	is read from a stream
     7.5  --
     7.6  
     7.7 +local Atom = require "tek.class.atom"
     7.8  local cgi = require "tek.class.cgi"
     7.9  local post = require "tek.class.cgi.post"
    7.10  
    7.11  local getfenv, setmetatable = getfenv, setmetatable
    7.12  local getenv, tonumber = os.getenv, tonumber
    7.13  local open, read = io.open, io.read
    7.14 -
    7.15 +local pairs = pairs
    7.16 +local tostring = tostring
    7.17 +local io = io
    7.18  
    7.19  module "tek.class.cgi.request"
    7.20  
    7.21 -
    7.22  _VERSION = 5
    7.23 -_REVISION = 1
    7.24 -
    7.25 +_REVISION = 2
    7.26  
    7.27  -------------------------------------------------------------------------------
    7.28 +--	Request class:
    7.29 +-------------------------------------------------------------------------------
    7.30  
    7.31 ---	Request class
    7.32 +local Request = Atom:newclass(getfenv())
    7.33  
    7.34 +function Request.new(class, self)
    7.35  
    7.36 -local Request = getfenv()
    7.37 +	self = Atom.new(class, self or { })
    7.38  
    7.39 +	self.maxinput = self.maxinput or 1048575
    7.40 +	self.maxfilesize = self.maxfilesize or 524288
    7.41 +	self.getenv = self.getenv or getenv
    7.42 +	self.read = self.read or read
    7.43  
    7.44 -function Request:new(o)
    7.45 -	o = o or { }
    7.46 -	o.maxinput = o.maxinput or 1048575
    7.47 -	o.maxfilesize = o.maxfilesize or 524288
    7.48 - 	setmetatable(o, self)
    7.49 - 	self.__index = self
    7.50 -	o.Protocol = getenv("SERVER_PROTOCOL")
    7.51 -	o.Port = tonumber(getenv("SERVER_PORT"))
    7.52 -	o.Method = getenv("REQUEST_METHOD")
    7.53 -	o.PathInfo = getenv("PATH_INFO")
    7.54 -	o.PathTranslated = getenv("PATH_TRANSLATED")
    7.55 -	o.ScriptName = getenv("SCRIPT_NAME")
    7.56 -	o.QueryString = getenv("QUERY_STRING")
    7.57 -	o.RemoteHost = getenv("REMOTE_HOST")
    7.58 -	o.RemoteAddr = getenv("REMOTE_ADDR")
    7.59 -	o.AuthType = getenv("AUTH_TYPE")
    7.60 -	o.RemoteUser = getenv("REMOTE_USER")
    7.61 -	o.RemoteIdent = getenv("REMOTE_IDENT")
    7.62 -	o.ContentType = getenv("CONTENT_TYPE")
    7.63 -	o.ContentLength = getenv("CONTENT_LENGTH")
    7.64 -	o.DocumentName = getenv("DOCUMENT_NAME")
    7.65 -	o.UniqueID = getenv("UNIQUE_ID")
    7.66 -	return o
    7.67 +	self.CONTENT_TYPE = self.getenv("CONTENT_TYPE")
    7.68 +	self.CONTENT_LENGTH = tonumber(self.getenv("CONTENT_LENGTH"))
    7.69 +	self.PATH_TRANSLATED = self.getenv("PATH_TRANSLATED")
    7.70 +	self.REQUEST_METHOD = self.getenv("REQUEST_METHOD")
    7.71 +	self.SERVER_PORT = tonumber(self.getenv("SERVER_PORT"))
    7.72 +	self.QUERY_STRING = self.getenv("QUERY_STRING")
    7.73 +	self.UNIQUE_ID = self.getenv("UNIQUE_ID")
    7.74 +	self.REQUEST_URI = self.getenv("REQUEST_URI")
    7.75 +	self.SCRIPT_FILENAME = self.getenv("SCRIPT_FILENAME")
    7.76 +
    7.77 +	return self
    7.78 +
    7.79  end
    7.80  
    7.81 -
    7.82  -------------------------------------------------------------------------------
    7.83 -
    7.84 ---	Arguments
    7.85 -
    7.86 +--	getargs:
    7.87 +-------------------------------------------------------------------------------
    7.88  
    7.89  function Request:getargs(maxinput, maxfilesize)
    7.90  	if not self.args then
    7.91  		self.args = { }
    7.92 -		if self.Method == "GET" then
    7.93 -			self.QueryString:gsub("([^&=]+)=([^&=]+)", function(key, value)
    7.94 +		if self.REQUEST_METHOD == "GET" then
    7.95 +			self.QUERY_STRING:gsub("([^&=]+)=([^&=]+)", function(key, value)
    7.96  				self.args[key] = cgi.decodeurl(value)
    7.97  			end)
    7.98 -		elseif self.Method == "POST" then
    7.99 +		elseif self.REQUEST_METHOD == "POST" then
   7.100  			post:new():parse {
   7.101 -				read = read,
   7.102 +				read = self.read,
   7.103  				discardinput = nil,
   7.104 -				content_type = self.ContentType,
   7.105 -				content_length = self.ContentLength,
   7.106 +				content_type = self.CONTENT_TYPE,
   7.107 +				content_length = self.CONTENT_LENGTH,
   7.108  				maxinput = maxinput or self.maxinput,
   7.109  				maxfilesize = maxfilesize or self.maxfilesize,
   7.110  				args = self.args
   7.111 @@ -84,24 +79,33 @@
   7.112  	return self.args
   7.113  end
   7.114  
   7.115 -
   7.116 +-------------------------------------------------------------------------------
   7.117 +--	getdocument: determine document properties
   7.118 +--	Name: Name of the script as part of the request
   7.119 +--	Handler: Name of script in the filesystem
   7.120 +--	VirtualPath: Path components past the script name
   7.121  -------------------------------------------------------------------------------
   7.122  
   7.123 ---	Document properties
   7.124 +function Request:getdocument(path)
   7.125  
   7.126 +	if not self.document and (path or self.PATH_TRANSLATED)
   7.127 +		and self.REQUEST_URI then
   7.128  
   7.129 -function Request:getdocument()
   7.130 +		-- determine script and virtual path components:
   7.131 +		local script, vpath = self.REQUEST_URI:match("^(/[^/]*)([^?]*)")
   7.132  
   7.133 -	if not self.document and self.PathTranslated then
   7.134 -	
   7.135 +		if not path then
   7.136 +			-- if no complete request path was supplied, construct it
   7.137 +			-- from REQUEST_URI and PATH_TRANSLATED:
   7.138 +			local scriptpat = script:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1")
   7.139 +			path = self.PATH_TRANSLATED:match(
   7.140 +				"(.*)" .. scriptpat .. vpath .. "$") or self.PATH_TRANSLATED
   7.141 +			path = path .. script .. vpath
   7.142 +		end
   7.143 +
   7.144  		self.document = { }
   7.145 -		
   7.146 -		--	Gather document information "Name" and "Path", additionally
   7.147 -		--	provide a "VirtualPath" that is past the end of the script path.
   7.148 -		--	"Handler" denotes the script in the path.
   7.149 -		
   7.150 -		local pt, vp, f = self.PathTranslated
   7.151 -		
   7.152 +		local pt, vp, f = path
   7.153 +
   7.154  		repeat
   7.155  			f = open(pt)
   7.156  			if f then
   7.157 @@ -113,49 +117,45 @@
   7.158  				vp = b .. (vp or "")
   7.159  				return a
   7.160  			end)
   7.161 -		until not pt
   7.162 -		
   7.163 +		until not pt or pt == ""
   7.164 +
   7.165  		self.document.Handler = pt
   7.166 -		
   7.167 -		local name = self.PathInfo
   7.168 +
   7.169  		if vp then
   7.170  			-- isolate document name by matching virtual path at end:
   7.171 -			if name:sub(-vp:len()) == vp then
   7.172 -				name = name:sub(1, name:len() - vp:len())
   7.173 +			if script:sub(-vp:len()) == vp then
   7.174 +				script = script:sub(1, script:len() - vp:len())
   7.175  			end
   7.176  			self.document.VirtualPath = vp:gsub("^/?(.*)$", "%1")
   7.177  		end
   7.178 -		self.document.Name = name
   7.179 -		self.document.Path = pt:gsub("^(.*/).-$", "%1")
   7.180 +
   7.181 +		self.document.Name = script
   7.182 +
   7.183  	end
   7.184 -	
   7.185 +
   7.186  	return self.document
   7.187  end
   7.188  
   7.189 -
   7.190  -------------------------------------------------------------------------------
   7.191 -
   7.192 ---	Header information
   7.193 -
   7.194 +--	getheader:
   7.195 +-------------------------------------------------------------------------------
   7.196  
   7.197  function Request:getheader()
   7.198  	if not self.header then
   7.199  		self.header = {
   7.200 -			Accept = getenv("HTTP_ACCEPT"),
   7.201 -			UserAgent = getenv("HTTP_USER_AGENT"),
   7.202 -			Referer = getenv("HTTP_REFERER"),
   7.203 -			Host = getenv("HTTP_HOST"),
   7.204 -			CookieString = getenv("HTTP_COOKIE"),
   7.205 +			Accept = self.getenv("HTTP_ACCEPT"),
   7.206 +			UserAgent = self.getenv("HTTP_USER_AGENT"),
   7.207 +			Referer = self.getenv("HTTP_REFERER"),
   7.208 +			Host = self.getenv("HTTP_HOST"),
   7.209 +			CookieString = self.getenv("HTTP_COOKIE"),
   7.210  		}
   7.211  	end
   7.212  	return self.header
   7.213  end
   7.214  
   7.215 -
   7.216  -------------------------------------------------------------------------------
   7.217 -
   7.218 ---	Cookies
   7.219 -
   7.220 +--	getcookies:
   7.221 +-------------------------------------------------------------------------------
   7.222  
   7.223  function Request:getcookies()
   7.224  	if not self.cookies then
     8.1 --- a/cgi-bin/tek/class/loona.lua	Mon May 28 11:24:23 2007 +0200
     8.2 +++ b/cgi-bin/tek/class/loona.lua	Fri Oct 05 01:41:59 2007 +0200
     8.3 @@ -5,6 +5,7 @@
     8.4  --	See copyright notice in COPYRIGHT
     8.5  --
     8.6  
     8.7 +local Atom = require "tek.class.atom"
     8.8  local lib = require "tek.lib"
     8.9  local luahtml = require "tek.lib.luahtml"
    8.10  local posix = require "tek.os.posix"
    8.11 @@ -13,7 +14,7 @@
    8.12  local util = require "tek.class.loona.util"
    8.13  local markup = require "tek.class.loona.markup"
    8.14  
    8.15 -local boxed_G = { 
    8.16 +local boxed_G = {
    8.17  	string = string, table = table,
    8.18  	assert = assert, collectgarbage = collectgarbage, dofile = dofile,
    8.19  	error = error, getfenv = getfenv, getmetatable = getmetatable,
    8.20 @@ -31,61 +32,58 @@
    8.21  local open, remove, rename, getenv, time, date =
    8.22  	io.open, os.remove, os.rename, os.getenv, os.time, os.date
    8.23  
    8.24 +-------------------------------------------------------------------------------
    8.25 +--	Module setup:
    8.26 +-------------------------------------------------------------------------------
    8.27  
    8.28  module "tek.class.loona"
    8.29  
    8.30 +_VERSION = 4
    8.31 +_REVISION = 4
    8.32  
    8.33 -_VERSION = 4
    8.34 -_REVISION = 2
    8.35 +-------------------------------------------------------------------------------
    8.36 +--	class Session:
    8.37 +-------------------------------------------------------------------------------
    8.38  
    8.39 +local Session = Atom:newclass()
    8.40  
    8.41 --- Session
    8.42 +function Session.new(class, self)
    8.43  
    8.44 +	self = Atom.new(class, self or { })
    8.45  
    8.46 -local Session = { }
    8.47 +	assert(self.id, "No session Id")
    8.48 + 	assert(self.sessiondir, "No session directory")
    8.49  
    8.50 -
    8.51 -function Session:new(o)
    8.52 -
    8.53 -	o = o or { }
    8.54 - 	setmetatable(o, self)
    8.55 - 	self.__index = self
    8.56 -	
    8.57 -	assert(o.id, "No session Id")
    8.58 - 	assert(o.sessiondir, "No session directory")
    8.59 -	
    8.60 -	o.name = o.id:gsub("(.)", function(a)
    8.61 +	self.name = self.id:gsub("(.)", function(a)
    8.62  		return ("%02x"):format(a:byte())
    8.63  	end)
    8.64 -	o.filename = o.sessiondir .. "/" .. o.name
    8.65 +	self.filename = self.sessiondir .. "/" .. self.name
    8.66  	-- remove non-dotted files (expired sessions) from sessions dir:
    8.67 -	util.expire(o.sessiondir, "[^.]%S+", o.maxage or 600)
    8.68 +	util.expire(self.sessiondir, "[^.]%S+", self.maxage or 600)
    8.69  	-- load session state:
    8.70 -	o.data = lib.source(o.filename) or { }
    8.71 -	
    8.72 -	return o
    8.73 +	self.data = lib.source(self.filename) or { }
    8.74 +
    8.75 +	return self
    8.76  end
    8.77  
    8.78 -
    8.79  function Session:save()
    8.80  	local f = open(self.filename, "wb")
    8.81  	assert(f, "Failed to open session file for writing")
    8.82 -	lib.dump(self.data, function(...) 
    8.83 -		f:write(unpack(arg)) 
    8.84 +	lib.dump(self.data, function(...)
    8.85 +		f:write(...)
    8.86  	end)
    8.87  	f:close()
    8.88  end
    8.89  
    8.90 -
    8.91  function Session:delete()
    8.92  	remove(self.filename)
    8.93  end
    8.94  
    8.95 +-------------------------------------------------------------------------------
    8.96 +--	class Loona
    8.97 +-------------------------------------------------------------------------------
    8.98  
    8.99 --- LOona
   8.100 -
   8.101 -
   8.102 -local Loona = getfenv()
   8.103 +local Loona = Atom:newclass(getfenv())
   8.104  
   8.105  
   8.106  function Loona:dbmsg(msg, detail)
   8.107 @@ -169,9 +167,9 @@
   8.108  function Loona:publishprofile(profile, lang)
   8.109  	lang = lang or self.lang
   8.110  	local contentdir = self.config.contentdir
   8.111 -	
   8.112 +
   8.113  	-- Get languages for the current profile
   8.114 -	
   8.115 +
   8.116  	local plangs = { }
   8.117  	local lmatch = "^" .. self.profile .. "_(%w+)$"
   8.118  	for e in util.readdir(self.config.contentdir) do
   8.119 @@ -180,35 +178,39 @@
   8.120  			table.insert(plangs, l)
   8.121  		end
   8.122  	end
   8.123 -	
   8.124 +
   8.125  	-- For all languages, update "current" symlink
   8.126 -	
   8.127 +
   8.128  	for _, lang in ipairs(plangs) do
   8.129  		self:makecurrent(profile, lang)
   8.130  	end
   8.131 -	
   8.132 +
   8.133  	-- These arguments are overwritten globally and need to get restored
   8.134 -	
   8.135 +
   8.136  	local save_args = { self.args.lang, self.args.profile, self.args.session }
   8.137 -	
   8.138 +
   8.139  	-- For all languages, unroll site to static HTML
   8.140 -	
   8.141 +
   8.142  	for _, lang in ipairs(plangs) do
   8.143  		local ext = (#plangs == 1 and ".html") or (".html." .. lang)
   8.144  		self:recursesections(self.sections, function(self, s, e, path)
   8.145  			path = path and path .. "/" .. e.name or e.name
   8.146  			if not e.notvisible then
   8.147 -				Loona:dumphtml { requestpath = path, requestlang = lang,
   8.148 -					htmlext = ext, insecure = true }
   8.149 +				Loona:dumphtml {
   8.150 +					request = self.request, -- reuse request
   8.151 +					userdata = self.userdata, -- reuse userdata
   8.152 +					requestpath = path, requestlang = lang,
   8.153 +					htmlext = ext, insecure = true
   8.154 +				}
   8.155  			end
   8.156  			return path
   8.157  		end)
   8.158  	end
   8.159 -	
   8.160 +
   8.161  	-- Restore arguments
   8.162 -	
   8.163 +
   8.164  	self.args.lang, self.args.profile, self.args.session = unpack(save_args)
   8.165 -	
   8.166 +
   8.167  	-- Update file cache
   8.168  
   8.169  	local htdocs = self.config.htdocsdir
   8.170 @@ -223,7 +225,7 @@
   8.171   				self:dbmsg("Could not purge cached HTML file", msg))
   8.172  		end
   8.173  	end
   8.174 -	
   8.175 +
   8.176  	for e in util.readdir(cache) do
   8.177  		local f = e:match("^(.*%.html%.?%w*)%.tmp$")
   8.178  		if f then
   8.179 @@ -250,9 +252,9 @@
   8.180  
   8.181  function Loona:indexsections()
   8.182  	self:recursesections(self.sections, function(self, s, e)
   8.183 -		e.notvalid = (not self.secure and e.secure) or 
   8.184 -			(not self.authuser and e.secret) or nil
   8.185 -		e.notvisible = e.notvalid or not self.authuser and e.hidden or nil
   8.186 +		e.notvalid = (not self.secure and e.secure) or
   8.187 +			(not self.authuser_visible and e.secret) or nil
   8.188 +		e.notvisible = e.notvalid or not self.authuser_visible and e.hidden or nil
   8.189  		s[e.name] = e
   8.190  	end)
   8.191  end
   8.192 @@ -313,7 +315,7 @@
   8.193  	local fullname = self.contentdir .. "/" .. fname
   8.194  	local success, msg = remove(fullname)
   8.195  	if all_bodies then
   8.196 -		local pat = "^" .. 
   8.197 +		local pat = "^" .. -- TODO: check
   8.198  			fname:gsub("%^%$%(%)%%%.%[%]%*%+%-%?", "%%%1") .. "%..*$"
   8.199  		for e in util.readdir(self.contentdir) do
   8.200  			if e:match(pat) then
   8.201 @@ -390,7 +392,7 @@
   8.202  					res, idx = tab, i
   8.203  					tab = tab[i].subs
   8.204  				else
   8.205 -					res, idx = nil, nil
   8.206 +					res, idx, tab = nil, nil, nil
   8.207  				end
   8.208  			end
   8.209  		end)
   8.210 @@ -415,7 +417,7 @@
   8.211  	end
   8.212  	self:out("<h2>Error</h2>")
   8.213  	self:out("<h3>" .. self:encodeform(ret[2] or "") .. "</h3>")
   8.214 -	if self.authuser then
   8.215 +	if self.authuser_debug then
   8.216  		if type(ret[3]) == "string" then
   8.217  			self:out("<p>" .. self:encodeform(ret[3]) .. "</p>")
   8.218  		end
   8.219 @@ -423,11 +425,11 @@
   8.220  			self:out("<pre>" .. self:encodeform(ret[4]) .. "</pre>")
   8.221  		end
   8.222  	end
   8.223 -end	
   8.224 +end
   8.225  
   8.226  
   8.227  function Loona:lockfile(file)
   8.228 -	return not self.session and true or 
   8.229 +	return not self.session and true or
   8.230  		posix.symlink(self.session.filename, file .. ".LOCK")
   8.231  end
   8.232  
   8.233 @@ -481,7 +483,7 @@
   8.234  	local f, msg = open(fname2)
   8.235  	assert(f, self:dbmsg("Cannot open file", msg))
   8.236  	local parsed, msg = self:loadhtml(f, "loona:out", fname2)
   8.237 -	assert(parsed, self:dbmsg("Syntax error", msg))
   8.238 +	assert(parsed, self:dbmsg("Syntax error", msg and msg.text))
   8.239  	return self:runboxed(parsed, nil, unpack(arg))
   8.240  end
   8.241  
   8.242 @@ -491,7 +493,7 @@
   8.243  function Loona:shref(section, arg)
   8.244  	local args2 = { } -- propagated or new arguments
   8.245  	for _, a in ipairs(arg) do
   8.246 -		local key, val = a:match("^(%w+)=(.*)$")
   8.247 +		local key, val = a:match("^([%w_]+)=(.*)$")
   8.248  		if key and val then -- "arg=val" sets/overrides argument
   8.249  			table.insert(args2, { name = key, value = val })
   8.250  		elseif self.args[a] then -- just "arg" propagates argument
   8.251 @@ -587,7 +589,7 @@
   8.252  	end
   8.253  	table.sort(tab)
   8.254  	for _, v in ipairs(tab) do
   8.255 -		tab[v] = v	
   8.256 +		tab[v] = v
   8.257  	end
   8.258  	return tab
   8.259  end
   8.260 @@ -649,9 +651,9 @@
   8.261  function Loona:menu(level, recurse, render)
   8.262  	level = level or 1
   8.263  	render = render or { }
   8.264 -	render.link = render.link or 
   8.265 +	render.link = render.link or
   8.266  		function(self, level, path, label, active, ...)
   8.267 -			self:out(('<a %shref="%s">%s</a>\n'):format(active and 
   8.268 +			self:out(('<a %shref="%s">%s</a>\n'):format(active and
   8.269  				'class="active" ' or "", self:href(path, unpack(arg)), label))
   8.270  		end
   8.271  	render.listbegin = render.listbegin or
   8.272 @@ -672,7 +674,8 @@
   8.273  		end
   8.274  	recurse = recurse == nil and true or recurse
   8.275  	local path = level > 1 and self:getpath("/", level - 1) or nil
   8.276 -	local addnew = self.authuser and not self.ispubprofile
   8.277 +	local addnew = self.authuser_menu and
   8.278 +		(not self.ispubprofile or self.config.editablepubprofile)
   8.279  	self:rmenu(level, render, path, addnew, recurse)
   8.280  end
   8.281  
   8.282 @@ -695,24 +698,25 @@
   8.283  
   8.284  
   8.285  function Loona:editable(editkey, fname, savename)
   8.286 -	
   8.287 +
   8.288  	local contentdir = self.contentdir
   8.289  	local edit, show, hidden, extramsg, changed
   8.290 -	
   8.291 -	if self.authuser then
   8.292 -		
   8.293 +
   8.294 +	if self.authuser_edit or self.authuser_profile or self.authuser_modifyprofile or self_authuser_attr
   8.295 +		or self.authuser_menu then
   8.296 +
   8.297  		local hiddenvars = table.concat( {
   8.298  			self:hidden("lang", self.args.lang),
   8.299  			self:hidden("profile", self.profile),
   8.300  			self:hidden("session", self.session.id),
   8.301  			self:hidden("editkey", editkey) }, " ")
   8.302 -	
   8.303 +
   8.304  		local lockfname = fname and (contentdir .. "/" .. fname)
   8.305 -		
   8.306 +
   8.307  		if self.useralert and editkey == self.args.editkey then
   8.308 -			
   8.309 +
   8.310  			--	display user alert/request/confirmation
   8.311 -			
   8.312 +
   8.313  			hidden = true
   8.314  			self:out([[
   8.315  			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   8.316 @@ -725,11 +729,11 @@
   8.317  				</fieldset>
   8.318  			</form>
   8.319  			]])
   8.320 -			
   8.321 -		elseif self.args.actionnew and editkey == "main" then
   8.322 -			
   8.323 +
   8.324 +		elseif self.args.actionnew and editkey == "main" and self.authuser_menu then
   8.325 +
   8.326  			--	form for creating a new section
   8.327 -			
   8.328 +
   8.329  			hidden = true
   8.330  			if self.ispubprofile then
   8.331  				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
   8.332 @@ -797,15 +801,15 @@
   8.333  								<input size="30" maxlength="50" name="editredirect" />
   8.334  							</td>
   8.335  						</tr>
   8.336 -					</table>					
   8.337 +					</table>
   8.338  					<input type="submit" name="actioncreate" value="]] .. self.locale.CREATE .. [[" />
   8.339  					]] .. hiddenvars .. [[
   8.340  				</fieldset>
   8.341  			</form>
   8.342  			<hr />
   8.343  			]])
   8.344 -		
   8.345 -		elseif self.args.actioneditprops and editkey == "main" then
   8.346 +
   8.347 +		elseif self.args.actioneditprops and editkey == "main" and self.authuser_attr then
   8.348  			hidden = true
   8.349  			if self.ispubprofile then
   8.350  				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
   8.351 @@ -872,62 +876,70 @@
   8.352  				</fieldset>
   8.353  			</form>
   8.354  			]])
   8.355 -		
   8.356 +
   8.357  		elseif (self.args.actioneditprofiles or
   8.358 -			self.args.actioncreateprofile or 
   8.359 -			self.args.actionchangeprofile or 
   8.360 +			self.args.actioncreateprofile or
   8.361 +			self.args.actionchangeprofile or
   8.362  			self.args.actionchangelanguage or
   8.363 -			self.args.actionpublishprofile) and editkey == "main" then
   8.364 +			self.args.actionpublishprofile) and editkey == "main" and
   8.365 +			(self.authuser_profile or self.authuser_modifyprofile) then
   8.366  			hidden = true
   8.367 -			self:out([[
   8.368 -			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   8.369 -				<fieldset>
   8.370 -					<legend>
   8.371 -						]] .. self.locale.CHANGEPROFILE .. [[
   8.372 -					</legend>
   8.373 -					<select name="changeprofile" size="1">]])
   8.374 -						for _, val in ipairs(self:getprofiles()) do
   8.375 -							self:out('<option' .. (val == self.profile and ' selected="selected"' or '') .. '>')
   8.376 -							self:out(val)
   8.377 -							self:out('</option>')
   8.378 -						end
   8.379 -					self:out([[
   8.380 -					</select>							
   8.381 -					<input type="submit" name="actionchangeprofile" value="]] .. self.locale.CHANGE ..[[" />
   8.382 -					]] .. hiddenvars .. [[
   8.383 -				</fieldset>
   8.384 -			</form>
   8.385 -			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   8.386 -				<fieldset>
   8.387 -					<legend>
   8.388 -						]] .. self.locale.CHANGELANGUAGE .. [[
   8.389 -					</legend>
   8.390 -					<select name="changelanguage" size="1">]])
   8.391 -						for _, val in ipairs(self:getlanguages()) do
   8.392 -							self:out('<option' .. (val == self.lang and ' selected="selected"' or '') .. '>')
   8.393 -							self:out(val)
   8.394 -							self:out('</option>')
   8.395 -						end
   8.396 -					self:out([[
   8.397 -					</select>							
   8.398 -					<input type="submit" name="actionchangelanguage" value="]] .. self.locale.CHANGE ..[[" />
   8.399 -					]] .. hiddenvars .. [[
   8.400 -				</fieldset>
   8.401 -			</form>
   8.402 -			<form action="]] .. self.document ..[[" method="post" accept-charset="utf-8">
   8.403 -				<fieldset>
   8.404 -					<legend>
   8.405 -						]] .. self.locale.CREATEPROFILE .. [[
   8.406 -					</legend>
   8.407 -					<input size="20" maxlength="20" name="createprofile" />
   8.408 -					]] .. self.locale.LANGUAGE ..[[
   8.409 -					<input size="2" maxlength="2" name="createlanguage" value="]] .. self.lang ..[[" />
   8.410 -					<input type="submit" name="actioncreateprofile" value="]] .. self.locale.CREATE .. [[" />
   8.411 -					]] .. hiddenvars .. [[
   8.412 -				</fieldset>
   8.413 -			</form>
   8.414 -			]])
   8.415 -			if not self.ispubprofile then
   8.416 +			if self.authuser_profile then
   8.417 +				self:out([[
   8.418 +				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   8.419 +					<fieldset>
   8.420 +						<legend>
   8.421 +							]] .. self.locale.CHANGEPROFILE .. [[
   8.422 +						</legend>
   8.423 +						<select name="changeprofile" size="1">]])
   8.424 +							for _, val in ipairs(self:getprofiles()) do
   8.425 +								self:out('<option' .. (val == self.profile and ' selected="selected"' or '') .. '>')
   8.426 +								self:out(val)
   8.427 +								self:out('</option>')
   8.428 +							end
   8.429 +						self:out([[
   8.430 +						</select>
   8.431 +						<input type="submit" name="actionchangeprofile" value="]] .. self.locale.CHANGE ..[[" />
   8.432 +						]] .. hiddenvars .. [[
   8.433 +					</fieldset>
   8.434 +				</form>
   8.435 +				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   8.436 +					<fieldset>
   8.437 +						<legend>
   8.438 +							]] .. self.locale.CHANGELANGUAGE .. [[
   8.439 +						</legend>
   8.440 +						<select name="changelanguage" size="1">]])
   8.441 +							for _, val in ipairs(self:getlanguages()) do
   8.442 +								self:out('<option' .. (val == self.lang and ' selected="selected"' or '') .. '>')
   8.443 +								self:out(val)
   8.444 +								self:out('</option>')
   8.445 +							end
   8.446 +						self:out([[
   8.447 +						</select>
   8.448 +						<input type="submit" name="actionchangelanguage" value="]] .. self.locale.CHANGE ..[[" />
   8.449 +						]] .. hiddenvars .. [[
   8.450 +					</fieldset>
   8.451 +				</form>
   8.452 +				]])
   8.453 +			end
   8.454 +			if self.authuser_modifyprofile then
   8.455 +				self:out([[
   8.456 +				<form action="]] .. self.document ..[[" method="post" accept-charset="utf-8">
   8.457 +					<fieldset>
   8.458 +						<legend>
   8.459 +							]] .. self.locale.CREATEPROFILE .. [[
   8.460 +						</legend>
   8.461 +						<input size="20" maxlength="20" name="createprofile" />
   8.462 +						]] .. self.locale.LANGUAGE ..[[
   8.463 +						<input size="2" maxlength="2" name="createlanguage" value="]] .. self.lang ..[[" />
   8.464 +						<input type="submit" name="actioncreateprofile" value="]] .. self.locale.CREATE .. [[" />
   8.465 +						]] .. hiddenvars .. [[
   8.466 +					</fieldset>
   8.467 +				</form>
   8.468 +				]])
   8.469 +			end
   8.470 +			if not self.ispubprofile or self.config.editablepubprofile and
   8.471 +				self.authuser_modifyprofile then
   8.472  				self:out([[
   8.473  				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   8.474  					<fieldset>
   8.475 @@ -951,23 +963,25 @@
   8.476  				</form>
   8.477  				]])
   8.478  			end
   8.479 -			
   8.480 +
   8.481  		elseif self.args.actionedit and editkey == self.args.editkey then
   8.482 -			if not self.section.redirect then
   8.483 +			if not self.section.redirect and self.authuser_edit then
   8.484  				extramsg = self.ispubprofile and
   8.485  					self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE
   8.486  				edit = self:loadcontent(fname):gsub("\194\160", "&nbsp;") -- TODO
   8.487  				changed = self.section and (self.section.revisiondate or self.section.creationdate)
   8.488  			end
   8.489 -		
   8.490 -		elseif self.args.actionpreview and editkey == self.args.editkey then
   8.491 +
   8.492 +		elseif self.args.actionpreview and editkey == self.args.editkey and
   8.493 +			self.authuser_edit then
   8.494  			edit = self.args.editform
   8.495  			show = self:domarkup(edit:gsub("&nbsp;", "\194\160")) -- TODO
   8.496 -		
   8.497 -		elseif self.args.actionsave and editkey == self.args.editkey then
   8.498 +
   8.499 +		elseif self.args.actionsave and editkey == self.args.editkey
   8.500 +			and self.authuser_edit then
   8.501  			local c = self.args.editform
   8.502  			local dynamic
   8.503 -			
   8.504 +
   8.505  			if lockfname then
   8.506  				self:expire(contentdir, "[^.]%S+.LOCK")
   8.507  				if self:lockfile(lockfname) then
   8.508 @@ -998,7 +1012,7 @@
   8.509  				-- TODO: error handling
   8.510  				show, dynamic = self:domarkup(savec)
   8.511  			end
   8.512 -			
   8.513 +
   8.514  			-- mark dynamic text bodies
   8.515  			if not self.section.dynamic then
   8.516  				self.section.dynamic = { }
   8.517 @@ -1011,21 +1025,21 @@
   8.518  			if n == 0 then
   8.519  				self.section.dynamic = nil
   8.520  			end
   8.521 -			
   8.522 +
   8.523  			self:saveindex()
   8.524 -			
   8.525 +
   8.526  		elseif self.args.actioncancel and editkey == self.args.editkey then
   8.527  			if lockfname then
   8.528  				self:unlockfile(lockfname) -- remove lock
   8.529  			end
   8.530  		end
   8.531 -		
   8.532 +
   8.533  		if editkey == "main" and self.section and self.section.redirect then
   8.534  			self:out('<h2>' .. self.locale.SECTION_IS_REDIRECT ..'</h2>')
   8.535  			self:out(self:link(self.section.redirect))
   8.536  			self:out('<hr />')
   8.537  		end
   8.538 -	
   8.539 +
   8.540  		if edit then
   8.541  			self:expire(contentdir, "[^.]%S+.LOCK")
   8.542  			if fname and not self:lockfile(contentdir .. "/" .. fname) then
   8.543 @@ -1055,8 +1069,8 @@
   8.544  			</form>
   8.545  			]])
   8.546  		end
   8.547 -	end	
   8.548 -	
   8.549 +	end
   8.550 +
   8.551  	if not hidden then
   8.552  		self:dosnippet(function()
   8.553  			if not show then
   8.554 @@ -1068,32 +1082,37 @@
   8.555  			self:runboxed(parsed)
   8.556  		end)
   8.557  	end
   8.558 -	
   8.559 -	if self.authuser then
   8.560 +
   8.561 +	if self.authuser_profile or self.authuser_edit or self.authuser_menu or self.authuser_attr then
   8.562  		self:out([[
   8.563  		<hr />
   8.564  		<div class="edit">]])
   8.565  			if editkey == "main" then
   8.566  				self:out([[
   8.567  				<a name="preview"></a>
   8.568 -				]] .. self.authuser .. [[ : 
   8.569 -				]] .. self:uilink(self.sectionpath, "[" .. self.locale.PROFILE .. "]", "actioneditprofiles=true", "editkey=" .. editkey) .. [[ :
   8.570 -				]] .. self.profile .. "_" .. self.lang)
   8.571 -				if self.ispubprofile then
   8.572 -					self:out([[
   8.573 -						<span class="warn">[]] .. self.locale.PUBLIC .. [[]</span>]])
   8.574 +				]] .. self.authuser .. [[ : ]])
   8.575 +				if self.authuser_profile then
   8.576 +					self:out(self:uilink(self.sectionpath, "[" .. self.locale.PROFILE .. "]", "actioneditprofiles=true", "editkey=" .. editkey) .. [[ :
   8.577 +						]] .. self.profile .. "_" .. self.lang)
   8.578 +					if self.ispubprofile then
   8.579 +						self:out([[
   8.580 +							<span class="warn">[]] .. self.locale.PUBLIC .. [[]</span>]])
   8.581 +					end
   8.582 +					self:out(" : ")
   8.583  				end
   8.584 -				self:out(' : ' .. self.sectionpath .. ' ')
   8.585 +				self:out(self.sectionpath .. ' ')
   8.586  			end
   8.587 -			if self.section and not self.ispubprofile then
   8.588 -				self:out(self:uilink(self.sectionpath, "[" .. self.locale.EDIT .. "]", "actionedit=true", "editkey=" .. editkey) .. " ")
   8.589 -				if editkey == "main" then
   8.590 +			if self.section and (not self.ispubprofile or self.config.editablepubprofile) then
   8.591 +				if self.authuser_edit then
   8.592 +					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.EDIT .. "]", "actionedit=true", "editkey=" .. editkey) .. " ")
   8.593 +				end
   8.594 +				if editkey == "main" and self.authuser_attr then
   8.595  					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.PROPERTIES .. "]", "actioneditprops=true", "editkey=" .. editkey) .. " ")
   8.596  				end
   8.597 -				if fname == savename or not self.section.subs then
   8.598 +				if (fname == savename or not self.section.subs) and (self.authuser_edit and self.authuser_menu) then
   8.599  					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.DELETE .. "]", "actiondelete=true", "editkey=" .. editkey) .. " ")
   8.600  				end
   8.601 -				if editkey == "main" then
   8.602 +				if editkey == "main" and self.authuser_menu then
   8.603  					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEUP .. "]", "actionup=true", "editkey=" .. editkey) .. " ")
   8.604  					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEDOWN .. "]", "actiondown=true", "editkey=" .. editkey) .. " ")
   8.605  				end
   8.606 @@ -1117,7 +1136,7 @@
   8.607  		if menu.entries and menu.entries[menu.name] then
   8.608  			table.insert(t, menu.name)
   8.609  			local fn = table.concat(t, "_")
   8.610 -			if posix.stat(self.contentdir .. "/" .. fn .. ext, 
   8.611 +			if posix.stat(self.contentdir .. "/" .. fn .. ext,
   8.612  				"mode") == "file" then
   8.613  				path, section = fn, menu
   8.614  			end
   8.615 @@ -1137,10 +1156,10 @@
   8.616  
   8.617  
   8.618  function Loona:init()
   8.619 -	
   8.620 +
   8.621  	-- get list of languages, in order of preference
   8.622  	-- TODO: respect quality parameter, not just order
   8.623 -	
   8.624 +
   8.625  	local l = self.requestlang or self.args.lang
   8.626  	self.langs = { l and l:match("^%w+$") }
   8.627  	local s = getenv("HTTP_ACCEPT_LANGUAGE")
   8.628 @@ -1153,16 +1172,16 @@
   8.629  		end
   8.630  	end
   8.631  	table.insert(self.langs, self.config.deflang)
   8.632 -	
   8.633 +
   8.634  	-- get list of possible profiles
   8.635 -	
   8.636 +
   8.637  	local profiles = { }
   8.638  	for e in util.readdir(self.config.contentdir) do
   8.639  		profiles[e] = e
   8.640  	end
   8.641 -	
   8.642 +
   8.643  	-- get pubprofile
   8.644 -	
   8.645 +
   8.646  	for _, lang in ipairs(self.langs) do
   8.647  		local p = posix.readlink(self.config.contentdir .. "/current_" .. lang)
   8.648  		p = p and p:match("^(%w+)_" .. lang .. "$")
   8.649 @@ -1171,11 +1190,11 @@
   8.650  			break
   8.651  		end
   8.652  	end
   8.653 -	
   8.654 +
   8.655  	-- get profile
   8.656 -	
   8.657 +
   8.658  	local checkprofile =
   8.659 -		self.authuser and self.args.profile or self.pubprofile or "work"
   8.660 +		self.authuser_profile and self.args.profile or self.pubprofile or "work"
   8.661  	for _, lang in ipairs(self.langs) do
   8.662  		if profiles[checkprofile .. "_" .. lang] then
   8.663  			self.profile = checkprofile
   8.664 @@ -1183,44 +1202,44 @@
   8.665  			break
   8.666  		end
   8.667  	end
   8.668 -	
   8.669 +
   8.670  	assert(self.profile and self.lang, "Invalid profile or language")
   8.671 -	
   8.672 -	
   8.673 +
   8.674 +
   8.675  	self.ispubprofile = self.profile == self.pubprofile
   8.676 -	
   8.677 +
   8.678  	-- write back language and profile
   8.679 -	
   8.680 +
   8.681  	self.args.lang = (self.explicitlang or self.lang ~= self.config.deflang)
   8.682  		and self.lang or nil
   8.683  	self.args.profile = self.profile
   8.684 -	
   8.685 +
   8.686  	-- determine content directory pathname and section filename
   8.687 -	
   8.688 +
   8.689  	self.contentdir =
   8.690  		("%s/%s_%s"):format(self.config.contentdir, self.profile, self.lang)
   8.691   	self.indexfname = self.contentdir .. "/.sections"
   8.692 -	
   8.693 +
   8.694  	-- load sections
   8.695 -	
   8.696 +
   8.697   	self.sections = lib.source(self.indexfname)
   8.698 -	
   8.699 +
   8.700  	-- index sections, determine visibility in menu
   8.701 -	
   8.702 +
   8.703  	self:indexsections()
   8.704 -	
   8.705 +
   8.706  	-- decompose request path, produce a stack of sections
   8.707 -	
   8.708 +
   8.709  	self.submenus, self.section = self:getsection(self.requestpath)
   8.710  
   8.711  	-- handle redirects if not logged on
   8.712 -	
   8.713 -	if not self.authuser and self.section and self.section.redirect then
   8.714 +
   8.715 +	if not self.authuser_edit and self.section and self.section.redirect then
   8.716  		self.submenus, self.section = self:getsection(self.section.redirect)
   8.717  	end
   8.718 -			
   8.719 +
   8.720  	-- section path and document name (refined)
   8.721 -	
   8.722 +
   8.723  	self.sectionpath = self:getpath()
   8.724  	self.sectionname = self:getpath("_")
   8.725  
   8.726 @@ -1228,22 +1247,22 @@
   8.727  
   8.728  
   8.729  function Loona:handlechanges()
   8.730 -	
   8.731 +
   8.732  	local save
   8.733  
   8.734  	if self.args.editkey == "main" then
   8.735 -		
   8.736 +
   8.737  		-- In main editable section:
   8.738 -		
   8.739 +
   8.740  		if self.args.actioncreate then
   8.741 -			
   8.742 +
   8.743  			-- Create new section
   8.744 -			
   8.745 +
   8.746  			local editname = self.args.editname:lower()
   8.747  			assert(not editname:match("%W"),
   8.748  				self:dbmsg("Invalid section name", editname))
   8.749  			if not (section and (section.subs or section)[editname]) then
   8.750 -				local newpath = (self.sectionpath and 
   8.751 +				local newpath = (self.sectionpath and
   8.752  					(self.sectionpath .. "/")) .. editname
   8.753  				local s = self:addpath(newpath, { name = editname,
   8.754  					label = self.args.editlabel ~= "" and
   8.755 @@ -1259,19 +1278,19 @@
   8.756  					creationdate = time() })
   8.757  				save = true
   8.758  			end
   8.759 -		
   8.760 +
   8.761  		elseif self.args.actionsave then
   8.762 -			
   8.763 +
   8.764  			-- Save section
   8.765 -			
   8.766 +
   8.767  			self.section.revisiondate = time()
   8.768  			self.section.revisioner = self.authuser
   8.769  			save = true
   8.770 - 		
   8.771 +
   8.772  		elseif self.args.actionsaveprops then
   8.773 -			
   8.774 +
   8.775  			-- Save properties
   8.776 -			
   8.777 +
   8.778  			self.section.hidden = self.args.editvisibility and true
   8.779  			self.section.secret = self.args.editsecrecy and true
   8.780  			self.section.secure = self.args.editsecure and true
   8.781 @@ -1282,15 +1301,15 @@
   8.782  			self.section.redirect =
   8.783  				self.args.editredirect ~= "" and self.args.editredirect or nil
   8.784  			save = true
   8.785 -		
   8.786 +
   8.787  		elseif self.args.actionup then
   8.788 -			
   8.789 +
   8.790  			-- Move section up
   8.791 -			
   8.792 +
   8.793  			local t, i = self:checkpath(self.sectionpath)
   8.794  			if t and i > 1 then
   8.795  				if self.ispubprofile and not self.args.actionconfirm then
   8.796 -					useralert = {
   8.797 +					self.useralert = {
   8.798  						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
   8.799  						confirm =
   8.800  							'<input type="submit" name="actionup" value="' ..
   8.801 @@ -1303,15 +1322,15 @@
   8.802  					save = true
   8.803  				end
   8.804  			end
   8.805 -		
   8.806 +
   8.807  		elseif self.args.actiondown then
   8.808 -			
   8.809 +
   8.810  			-- Move section down
   8.811 -			
   8.812 +
   8.813  			local t, i = self:checkpath(self.sectionpath)
   8.814  			if t and i < #t then
   8.815  				if self.ispubprofile and not self.args.actionconfirm then
   8.816 -					useralert = {
   8.817 +					self.useralert = {
   8.818  						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
   8.819  						confirm =
   8.820  							'<input type="submit" name="actiondown" value="' ..
   8.821 @@ -1324,11 +1343,11 @@
   8.822  					save = true
   8.823  				end
   8.824  			end
   8.825 -		
   8.826 +
   8.827  		elseif self.args.actioncreateprofile and self.args.createprofile then
   8.828 -			
   8.829 +
   8.830  			-- Create profile
   8.831 -			
   8.832 +
   8.833  			local c = self.args.createprofile
   8.834  			if c == "" then
   8.835  				c = self.profile
   8.836 @@ -1336,7 +1355,7 @@
   8.837  			c = self:checkprofilename(c:lower())
   8.838  			local l = self:checklanguage((self.args.createlanguage or self.lang):lower())
   8.839  			if c == self.profile and l == self.lang then
   8.840 -				useralert = { 
   8.841 +				self.useralert = {
   8.842  					text = self.locale.ALERT_CANNOT_COPY_PROFILE_TO_SELF,
   8.843  					returnto = self:hidden("actioneditprofiles", "true")
   8.844  				}
   8.845 @@ -1349,7 +1368,7 @@
   8.846  					text = self.locale.ALERT_OVERWRITE_EXISTING_PROFILE
   8.847  				end
   8.848  				if text and not self.args.actionconfirm then
   8.849 -					useralert = {
   8.850 +					self.useralert = {
   8.851  						text = text,
   8.852  						returnto = self:hidden("actioneditprofiles", "true"),
   8.853  						confirm = '<input type="submit" ' ..
   8.854 @@ -1366,11 +1385,11 @@
   8.855  					self:copyprofile(c, self.profile, l, self.lang)
   8.856  				end
   8.857  			end
   8.858 -		
   8.859 +
   8.860  		elseif self.args.actiondeleteprofile and self.args.deleteprofile then
   8.861 -			
   8.862 +
   8.863  			-- Delete profile
   8.864 -			
   8.865 +
   8.866  			local c = self:checkprofilename(self.args.deleteprofile:lower())
   8.867  			assert(c ~= self.pubprofile,
   8.868  				self:dbmsg("Cannot delete published profile", c))
   8.869 @@ -1381,47 +1400,47 @@
   8.870  				self:init()
   8.871  				save = true
   8.872  			else
   8.873 -				useralert = { 
   8.874 +				self.useralert = {
   8.875  					text = self.locale.ALERT_DELETE_PROFILE,
   8.876  					returnto = self:hidden("actioneditprofiles", "true"),
   8.877  					confirm = '<input type="submit" ' ..
   8.878 -						'name="actiondeleteprofile" value="' .. 
   8.879 +						'name="actiondeleteprofile" value="' ..
   8.880  						self.locale.DELETE .. '" /> ' ..
   8.881  						self:hidden("actionconfirm", "true") ..
   8.882  						self:hidden("deleteprofile", c)
   8.883  				}
   8.884  			end
   8.885 -		
   8.886 +
   8.887  		elseif self.args.actionchangeprofile and self.args.changeprofile then
   8.888 -			
   8.889 +
   8.890  			-- Change profile
   8.891 -			
   8.892 +
   8.893  			local c = self:checkprofilename(self.args.changeprofile:lower())
   8.894  			self.profile = c
   8.895  			self.args.profile = c
   8.896  			save = true
   8.897 -		
   8.898 +
   8.899  		elseif self.args.actionchangelanguage and self.args.changelanguage then
   8.900 -			
   8.901 +
   8.902  			-- Change language
   8.903 -			
   8.904 +
   8.905  			local l = self:checklanguage(self.args.changelanguage:lower())
   8.906  			self.lang = l
   8.907  			self.args.lang = l
   8.908   			self.explicitlang = l
   8.909  			save = true
   8.910 -		
   8.911 +
   8.912  		elseif self.args.actionpublishprofile and self.args.publishprofile then
   8.913 -			
   8.914 +
   8.915  			-- Publish profile
   8.916 -			
   8.917 +
   8.918  			local c = self:checkprofilename(self.args.publishprofile:lower())
   8.919  			if c ~= self.publicprofile then
   8.920  				if self.args.actionconfirm then
   8.921  					self:publishprofile(c)
   8.922  					save = true
   8.923  				else
   8.924 -					useralert = {
   8.925 +					self.useralert = {
   8.926  						text = self.locale.ALERT_PUBLISH_PROFILE,
   8.927  						returnto = self:hidden("actioneditprofiles", "true"),
   8.928  						confirm = '<input type="submit" ' ..
   8.929 @@ -1433,20 +1452,20 @@
   8.930  				end
   8.931  			end
   8.932  		end
   8.933 -		
   8.934 +
   8.935  	end
   8.936 -	
   8.937 +
   8.938  	if self.args.actiondelete then
   8.939 -		
   8.940 +
   8.941  		-- Delete section
   8.942 -		
   8.943 +
   8.944  		if not self.args.actionconfirm then
   8.945 -			useralert = {
   8.946 +			self.useralert = {
   8.947  				text = self.ispubprofile and
   8.948  					self.locale.ALERT_DELETE_IN_PUBLISHED_PROFILE or
   8.949  					self.locale.ALERT_DELETE_SECTION,
   8.950  				confirm =
   8.951 -					'<input type="submit" name="actiondelete" value="' .. 
   8.952 +					'<input type="submit" name="actiondelete" value="' ..
   8.953  					self.locale.DELETE .. '" /> ' ..
   8.954  					self:hidden("actionconfirm", "true")
   8.955  			}
   8.956 @@ -1472,12 +1491,12 @@
   8.957  			save = true
   8.958  		end
   8.959  	end
   8.960 -		
   8.961 +
   8.962  	if save then
   8.963  		self:saveindex()
   8.964  		self:init()
   8.965  	end
   8.966 -	
   8.967 +
   8.968  end
   8.969  
   8.970  
   8.971 @@ -1501,51 +1520,48 @@
   8.972  end
   8.973  
   8.974  
   8.975 -function Loona:new(o)
   8.976 +function Loona.new(class, self)
   8.977 +
   8.978 +	self = Atom.new(class, self or { })
   8.979  
   8.980  	local parsed, msg
   8.981 -	
   8.982 -	o = o or { }
   8.983 -	setmetatable(o, self)
   8.984 -	self.__index = self
   8.985 --- 	o = atom.new(self, o)
   8.986 -	
   8.987 +
   8.988  	-- Buffer
   8.989 -	
   8.990 -	o.out = o.out or function(self, s)
   8.991 +
   8.992 +	self.out = self.out or function(self, s)
   8.993  		self.buf:out(s)
   8.994  	end
   8.995 -	o.setheader = o.setheader or function(self, s)
   8.996 -		self.buf:setheader(s)
   8.997 +	self.addheader = self.addheader or function(self, s)
   8.998 +		self.buf:addheader(s)
   8.999  	end
  8.1000 -	
  8.1001 +
  8.1002  	-- Get configuration
  8.1003 -	
  8.1004 -	o.config = o.config or lib.source(o.conffile or "../etc/config.lua") or { }
  8.1005 -	o.config.defname = o.config.defname or "home"
  8.1006 -	o.config.deflang = o.config.deflang or "en"
  8.1007 -	o.config.sessionmaxage = o.config.sessionmaxage or 6000
  8.1008 -	o.config.secureport = o.config.secureport or 443
  8.1009 -	o.config.passwdfile =
  8.1010 -		posix.abspath(o.config.passwdfile or "../etc/passwd.lua")
  8.1011 -	o.config.sessiondir =
  8.1012 -		posix.abspath(o.config.sessiondir or "../var/sessions")
  8.1013 -	o.config.extdir = posix.abspath(o.config.extdir or "../extensions")
  8.1014 -	o.config.contentdir = posix.abspath(o.config.contentdir or "../content")
  8.1015 -	o.config.localedir = posix.abspath(o.config.localedir or "../locale")
  8.1016 -	o.config.htdocsdir = posix.abspath(o.config.htdocsdir or "../htdocs")
  8.1017 -	o.config.htmlcachedir = 
  8.1018 -		posix.abspath(o.config.htmlcachedir or "../var/htmlcache")
  8.1019 -	o.config.extlinkextra = o.config.extlinksamewindow and ' class="extlink"'
  8.1020 +
  8.1021 +	self.config = self.config or lib.source(self.conffile or "../etc/config.lua") or { }
  8.1022 +	self.config.defname = self.config.defname or "home"
  8.1023 +	self.config.deflang = self.config.deflang or "en"
  8.1024 +	self.config.sessionmaxage = self.config.sessionmaxage or 6000
  8.1025 +	self.config.secureport = self.config.secureport or 443
  8.1026 +	self.config.passwdfile =
  8.1027 +		posix.abspath(self.config.passwdfile or "../etc/passwd.lua")
  8.1028 +	self.config.sessiondir =
  8.1029 +		posix.abspath(self.config.sessiondir or "../var/sessions")
  8.1030 +	self.config.extdir = posix.abspath(self.config.extdir or "../extensions")
  8.1031 +	self.config.contentdir = posix.abspath(self.config.contentdir or "../content")
  8.1032 +	self.config.localedir = posix.abspath(self.config.localedir or "../locale")
  8.1033 +	self.config.htdocsdir = posix.abspath(self.config.htdocsdir or "../htdocs")
  8.1034 +	self.config.htmlcachedir =
  8.1035 +		posix.abspath(self.config.htmlcachedir or "../var/htmlcache")
  8.1036 +	self.config.extlinkextra = self.config.extlinksamewindow and ' class="extlink"'
  8.1037  		or ' class="extlink" onclick="void(window.open(this.href, \'\', \'\')); return false;"'
  8.1038 -	
  8.1039 +
  8.1040  	-- Create proxy for on-demand loading of locales
  8.1041 -	
  8.1042 -	o.locale = { }
  8.1043 +
  8.1044 +	self.locale = { }
  8.1045  	local locmt = { }
  8.1046  	locmt.__index = function(_, key)
  8.1047 -		for _, l in ipairs(o.langs) do
  8.1048 -			locmt.__locale = lib.source(o.config.localedir .. "/" .. l)
  8.1049 +		for _, l in ipairs(self.langs) do
  8.1050 +			locmt.__locale = lib.source(self.config.localedir .. "/" .. l)
  8.1051  			if locmt.__locale then
  8.1052  				break
  8.1053  			end
  8.1054 @@ -1555,91 +1571,110 @@
  8.1055  		end
  8.1056  		return locmt.__locale[key] or key
  8.1057  	end
  8.1058 -	setmetatable(o.locale, locmt)
  8.1059 -	
  8.1060 +	setmetatable(self.locale, locmt)
  8.1061 +
  8.1062  	-- Get request, args, document, script name, request path
  8.1063 -	
  8.1064 -	o.request = o.request or Request:new()
  8.1065 -	o.args = o.request:getargs()
  8.1066 -	o.cgi_document = o.request:getdocument()
  8.1067 - 	
  8.1068 - 	o.scriptpath = o.scriptpath or o.cgi_document.Path
  8.1069 -	o.requesthandler = o.requesthandler or o.cgi_document.Handler
  8.1070 - 	o.requestdocument = o.requestdocument or o.cgi_document.Name
  8.1071 -	o.requestpath = o.requestpath or o.cgi_document.VirtualPath
  8.1072 -	o.explicitlang = not o.requestlang and o.args.lang
  8.1073 -	o.secure = not o.insecure and (o.request.Port == o.config.secureport)
  8.1074 +
  8.1075 +	self.request = self.request or Request:new()
  8.1076 +	self.args = self.request:getargs()
  8.1077 +	self.cgi_document = self.request:getdocument()
  8.1078 +
  8.1079 +--  	self.scriptpath = self.scriptpath or self.cgi_document.Path
  8.1080 +	self.requesthandler = self.requesthandler or self.cgi_document.Handler
  8.1081 + 	self.requestdocument = self.requestdocument or self.cgi_document.Name
  8.1082 +	self.requestpath = self.requestpath or self.cgi_document.VirtualPath
  8.1083 +	self.explicitlang = not self.requestlang and self.args.lang
  8.1084 +	self.secure = not self.insecure and (self.request.SERVER_PORT == self.config.secureport)
  8.1085  
  8.1086  	-- Manage login and establish session
  8.1087 -	
  8.1088 -	if not o.nologin then
  8.1089 -		local sid = o.args.session or o.request.UniqueID
  8.1090 -		o.session = o.session or Session:new {
  8.1091 +
  8.1092 +	if not self.nologin then
  8.1093 +		local sid = self.args.session or self.request.UNIQUE_ID
  8.1094 +		self.session = self.session or Session:new {
  8.1095  			id = sid,
  8.1096 -			sessiondir = o.config.sessiondir,
  8.1097 -			maxage = o.config.sessionmaxage
  8.1098 +			sessiondir = self.config.sessiondir,
  8.1099 +			maxage = self.config.sessionmaxage
  8.1100  		}
  8.1101 -		if o.args.login then
  8.1102 +		if self.args.login then
  8.1103  			-- write back session ID into request args:
  8.1104 -			o.args.session = sid -- !
  8.1105 -			if o.args.login == "false" then
  8.1106 -				o.session:delete()
  8.1107 -				o.session = nil
  8.1108 -			elseif o.args.password then
  8.1109 -				o.loginfailed = true
  8.1110 -				local pwddb = lib.source(o.config.passwdfile)
  8.1111 -				local pwdentry = pwddb[o.args.login]
  8.1112 -				if pwdentry and pwdentry.password == o.args.password then
  8.1113 -					o.session.data.authuser = pwdentry.username
  8.1114 -					o.session.data.id = o.session.id
  8.1115 -					o.loginfailed = nil
  8.1116 +			self.args.session = sid -- !
  8.1117 +			if self.args.login == "false" then
  8.1118 +				self.session:delete()
  8.1119 +				self.session = nil
  8.1120 +			elseif self.args.password then
  8.1121 +				self.loginfailed = true
  8.1122 +				local match, username, perm =
  8.1123 +					self:checkpw(self.args.login, self.args.password)
  8.1124 +				if match then
  8.1125 +					self.session.data.authuser = self.args.login
  8.1126 +					self.session.data.username = username
  8.1127 +					self.session.data.permissions = perm
  8.1128 +					self.session.data.id = self.session.id
  8.1129 +					self.loginfailed = nil
  8.1130  				end
  8.1131  			end
  8.1132  		end
  8.1133 -		o.authuser = o.session and o.session.data.authuser
  8.1134 +		self.authuser = self.session and self.session.data.authuser
  8.1135  	end
  8.1136  
  8.1137 -	if o.nologin or not o.authuser then
  8.1138 -		o.authuser = nil
  8.1139 -		o.session = nil
  8.1140 -		o.args.session = nil
  8.1141 +	if self.nologin or not self.authuser then
  8.1142 +		self.authuser = nil
  8.1143 +		self.session = nil
  8.1144 +		self.args.session = nil
  8.1145 +	else
  8.1146 +		self.authuser_edit = self.session.data.permissions:find("e") and true
  8.1147 +		self.authuser_menu = self.session.data.permissions:find("m") and true
  8.1148 +		self.authuser_attr = self.session.data.permissions:find("a") and true
  8.1149 +		self.authuser_profile = self.session.data.permissions:find("p") and true
  8.1150 +		self.authuser_modifyprofile = self.session.data.permissions:find("c") and true
  8.1151 +		self.authuser_visible = self.session.data.permissions:find("v") and true
  8.1152 +		self.authuser_debug = self.session.data.permissions:find("d") and true
  8.1153  	end
  8.1154  
  8.1155  	-- Get lang, locale, profile, section
  8.1156  
  8.1157 -	o:init()
  8.1158 -	if o.authuser then
  8.1159 -		o:handlechanges()
  8.1160 +	self:init()
  8.1161 +	if self.authuser then -- TODO?
  8.1162 +		self:handlechanges()
  8.1163  	else
  8.1164 -		o.args.profile = nil
  8.1165 +		self.args.profile = nil
  8.1166  	end
  8.1167 -	
  8.1168 +
  8.1169  	-- Current document
  8.1170 -	
  8.1171 -	o.document = o.requestdocument .. "/" .. o.sectionpath
  8.1172 -	if o.authuser then
  8.1173 -		o.getdocname = function(self, path)
  8.1174 +
  8.1175 +	self.document = self.requestdocument .. "/" .. self.sectionpath
  8.1176 +	if self.authuser then
  8.1177 +		self.getdocname = function(self, path)
  8.1178  			return self.requestdocument .. "/" .. (path or self.sectionpath)
  8.1179  		end
  8.1180  	else
  8.1181 -		o.getdocname = function(self, path, haveargs)
  8.1182 -			local dyn
  8.1183 -			dyn, path = self:isdynamic(path or self.sectionpath)
  8.1184 -			if dyn or haveargs then
  8.1185 +		self.getdocname = function(self, path, haveargs)
  8.1186 +			local dyn, exists
  8.1187 +			dyn, path, exists = self:isdynamic(path or self.sectionpath)
  8.1188 +			if dyn or haveargs or not exists then
  8.1189  				return self.requestdocument .. "/" .. path
  8.1190  			end
  8.1191  			path = path == self.config.defname and "index" or path
  8.1192  			return "/" .. path:gsub("/", "_") .. ".html"
  8.1193  		end
  8.1194  	end
  8.1195 -	
  8.1196 +
  8.1197  	-- Save session state
  8.1198 -	
  8.1199 -	if o.session then
  8.1200 -		o.session:save()
  8.1201 +
  8.1202 +	if self.session then
  8.1203 +		self.session:save()
  8.1204  	end
  8.1205 -	
  8.1206 -	return o
  8.1207 +
  8.1208 +	return self
  8.1209 +end
  8.1210 +
  8.1211 +
  8.1212 +function Loona:checkpw(login, passwd)
  8.1213 +	local pwddb = lib.source(self.config.passwdfile)
  8.1214 +	local pwdentry = pwddb[login]
  8.1215 +	if pwdentry and pwdentry.password == passwd then
  8.1216 +		return true, pwdentry.username, pwdentry.permissions or ""
  8.1217 +	end
  8.1218  end
  8.1219  
  8.1220  
  8.1221 @@ -1686,12 +1721,16 @@
  8.1222  
  8.1223  function Loona:isdynamic(path)
  8.1224  	path = path or self.sectionpath
  8.1225 +	local exists
  8.1226  	local t, i = self:checkpath(path)
  8.1227 -	if t and t[i].redirect then
  8.1228 -		path = t[i].redirect
  8.1229 -		t, i = self:isdynamic(path) -- TODO: prohibit endless recursion
  8.1230 +	if t then
  8.1231 +		exists = true
  8.1232 +		if t[i].redirect then
  8.1233 +			path = t[i].redirect
  8.1234 +			t, i, exists = self:isdynamic(path) -- TODO: prohibit endless recursion
  8.1235 +		end
  8.1236  	end
  8.1237 -	return t and t[i].dynamic, path
  8.1238 +	return t and t[i].dynamic, path, exists
  8.1239  end
  8.1240  
  8.1241  
  8.1242 @@ -1700,7 +1739,8 @@
  8.1243  	o = o or { }
  8.1244  	o.nologin = true
  8.1245  	o.out = function(self, s) table.insert(outbuf, s) end
  8.1246 -	o.setheader = function(self, s) end
  8.1247 +	o.addheader = function(self, s) end
  8.1248 +
  8.1249  	o = self:new(o):run()
  8.1250  	if not o:isdynamic() then
  8.1251  		local path = o.sectionname
  8.1252 @@ -1708,10 +1748,12 @@
  8.1253  		local srcname = o.config.htdocsdir .. "/" .. path .. o.htmlext
  8.1254  		local fh, msg = open(srcname .. ".tmp", "wb")
  8.1255  		assert(fh, self:dbmsg("Could not write cached HTML", msg))
  8.1256 -		fh:write(unpack(outbuf))
  8.1257 +		for _, line in ipairs(outbuf) do
  8.1258 +			fh:write(line)
  8.1259 +		end
  8.1260  		fh:close()
  8.1261  		local dstname = o.config.htmlcachedir .. "/" .. path .. o.htmlext
  8.1262  		local success, msg = posix.symlink(srcname, dstname .. ".tmp")
  8.1263 --- 		assert(success, self:dbmsg("Could not link to cached HTML", msg))
  8.1264 +		-- assert(success, self:dbmsg("Could not link to cached HTML", msg))
  8.1265  	end
  8.1266  end
     9.1 --- a/cgi-bin/tek/class/loona/buffer.lua	Mon May 28 11:24:23 2007 +0200
     9.2 +++ b/cgi-bin/tek/class/loona/buffer.lua	Fri Oct 05 01:41:59 2007 +0200
     9.3 @@ -1,52 +1,63 @@
     9.4  
     9.5  --
     9.6 ---	tek.class.loona.buffer - LOona outbuffer class
     9.7 +--	tek.class.loona.buffer - LOona output buffer class
     9.8  --	Written by Timm S. Mueller <tmueller at neoscientists.org>
     9.9  --	See copyright notice in COPYRIGHT
    9.10  --
    9.11  
    9.12 +local Atom = require "tek.class.atom"
    9.13 +
    9.14  local getfenv, setmetatable, unpack, insert, stdout =
    9.15  	getfenv, setmetatable, unpack, table.insert, io.stdout
    9.16  
    9.17 -
    9.18  module "tek.class.loona.buffer"
    9.19  
    9.20 -
    9.21 -_VERSION = 1
    9.22 +_VERSION = 2
    9.23  _REVISION = 0
    9.24  
    9.25 +-------------------------------------------------------------------------------
    9.26 +--	Buffer class:
    9.27 +-------------------------------------------------------------------------------
    9.28  
    9.29 -local buffer = getfenv()
    9.30 +local Buffer = Atom:newclass(getfenv())
    9.31  
    9.32 +function Buffer.new(class, self)
    9.33 +	self = Atom.new(class, self or { })
    9.34 +	self.outbuf = self.outbuf or { }
    9.35 +	self.headerbuf = self.headerbuf or { }
    9.36 +	return self
    9.37 +end
    9.38  
    9.39 -function buffer:out(s)
    9.40 +-------------------------------------------------------------------------------
    9.41 +--	out: the output function called by an application
    9.42 +-------------------------------------------------------------------------------
    9.43 +
    9.44 +function Buffer:out(s)
    9.45  	insert(self.outbuf, s)
    9.46  end
    9.47  
    9.48 +-------------------------------------------------------------------------------
    9.49 +--	addheader:
    9.50 +-------------------------------------------------------------------------------
    9.51  
    9.52 -function buffer:setheader(s)
    9.53 +function Buffer:addheader(s)
    9.54  	insert(self.headerbuf, 1, s)
    9.55  end
    9.56  
    9.57 +-------------------------------------------------------------------------------
    9.58 +--	discard:
    9.59 +-------------------------------------------------------------------------------
    9.60  
    9.61 -function buffer:discard()
    9.62 +function Buffer:discard()
    9.63  	self.headerbuf = { }
    9.64  	self.outbuf = { }
    9.65  end
    9.66  
    9.67 +-------------------------------------------------------------------------------
    9.68 +--	flush: overwrite this function to redirect the output
    9.69 +-------------------------------------------------------------------------------
    9.70  
    9.71 -function buffer:flush(discard)
    9.72 +function Buffer:flush()
    9.73  	stdout:write(unpack(self.headerbuf))
    9.74  	stdout:write(unpack(self.outbuf))
    9.75 -	if discard ~= false then
    9.76 -		self:discard()
    9.77 -	end
    9.78  end
    9.79 -
    9.80 -
    9.81 -function buffer:new()
    9.82 -	local o = { outbuf = { }, headerbuf = { } }
    9.83 -	setmetatable(o, self)
    9.84 -	self.__index = self
    9.85 -	return o
    9.86 -end
    10.1 --- a/cgi-bin/tek/class/loona/markup.lua	Mon May 28 11:24:23 2007 +0200
    10.2 +++ b/cgi-bin/tek/class/loona/markup.lua	Fri Oct 05 01:41:59 2007 +0200
    10.3 @@ -237,13 +237,13 @@
    10.4  local function parse(state, ...)
    10.5  
    10.6  	--	do a generator function by ID
    10.7 -	
    10.8 +
    10.9  	local function doid(id, ...)
   10.10  		if state.genfunc and state.genfunc[id] then
   10.11  			return state.genfunc[id](state, unpack(arg))
   10.12  		end
   10.13  	end
   10.14 -	
   10.15 +
   10.16  	local function doout(id, ...)
   10.17  		doid("out", doid(id, unpack(arg)))
   10.18  	end
   10.19 @@ -331,7 +331,7 @@
   10.20  	end
   10.21  
   10.22  	local function pushfragment(...)
   10.23 -		line = concat(arg)
   10.24 +		local line = concat(arg)
   10.25  		if not state.context.fragments then
   10.26  			state.context.fragments = { }
   10.27  		end
   10.28 @@ -402,8 +402,8 @@
   10.29  				state.table = nil
   10.30  			end
   10.31  		end
   10.32 -		
   10.33 -		
   10.34 +
   10.35 +
   10.36  		if state.indent < state.previndent then
   10.37  			if not state.preindent or state.indent < state.preindent then
   10.38  				local i = state.indent
   10.39 @@ -443,7 +443,7 @@
   10.40  					return ""
   10.41  				end
   10.42  			end)
   10.43 -			
   10.44 +
   10.45  			-- argument ( ======== )
   10.46  
   10.47  			line = line:gsub("^%s*========+%s*$", function()
   10.48 @@ -491,7 +491,7 @@
   10.49  						state.prepend = true
   10.50  					else
   10.51  						push("preline")
   10.52 -						doout("getpre", (" "):rep(state.indent - 
   10.53 +						doout("getpre", (" "):rep(state.indent -
   10.54  							state.preindent) .. line)
   10.55  						pop()
   10.56  					end
    11.1 --- a/cgi-bin/tek/lib.lua	Mon May 28 11:24:23 2007 +0200
    11.2 +++ b/cgi-bin/tek/lib.lua	Fri Oct 05 01:41:59 2007 +0200
    11.3 @@ -87,7 +87,7 @@
    11.4  
    11.5  -------------------------------------------------------------------------------
    11.6  
    11.7 ---	Recursive serialize - 
    11.8 +--	Recursive serialize -
    11.9  --	note that cyclic dependencies are silently dropped
   11.10  
   11.11  local function serialize(tab, sorted, indent, outfunc, saved)
   11.12 @@ -118,7 +118,7 @@
   11.13  	for _, e in ipairs(set) do
   11.14  		local key, val = e.key, e.val
   11.15  		local t = type(val)
   11.16 -		
   11.17 +
   11.18  		if not saved[val] then
   11.19  			outfunc(is)
   11.20  			if type(key) == "number" then
   11.21 @@ -135,7 +135,7 @@
   11.22  				serialize(val, sorted, indent + 1, outfunc, saved)
   11.23  				outfunc(is .. '},\n')
   11.24  			elseif t == "string" then
   11.25 -				outfunc('"' .. val:gsub("([%z\001-\031])", function(c)
   11.26 +				outfunc('"' .. val:gsub('([%z\001-\031"])', function(c)
   11.27  					return ("\\%03d"):format(c:byte())
   11.28  				end) .. '",\n')
   11.29  			elseif t == "number" or t == "boolean" then
   11.30 @@ -163,29 +163,29 @@
   11.31  	if c < 128 then
   11.32  		return char(c)
   11.33  	elseif c < 2048 then
   11.34 -		return char(192 + floor(c / 64), 
   11.35 +		return char(192 + floor(c / 64),
   11.36  			128 + c % 64)
   11.37  	elseif c < 65536 then
   11.38 -		return char(224 + floor(c / 4096), 
   11.39 -			128 + floor((c % 4096) / 64), 
   11.40 +		return char(224 + floor(c / 4096),
   11.41 +			128 + floor((c % 4096) / 64),
   11.42  			128 + c % 64)
   11.43  	elseif c < 2097152 then
   11.44 -		return char(240 + floor(c / 262144), 
   11.45 -			128 + floor((c % 262144) / 4096), 
   11.46 -			128 + floor((c % 4096) / 64), 
   11.47 +		return char(240 + floor(c / 262144),
   11.48 +			128 + floor((c % 262144) / 4096),
   11.49 +			128 + floor((c % 4096) / 64),
   11.50  			128 + c % 64)
   11.51  	elseif c < 67108864 then
   11.52 -		return char(248 + floor(c / 16777216), 
   11.53 -			128 + floor((c % 16777216) / 262144), 
   11.54 +		return char(248 + floor(c / 16777216),
   11.55 +			128 + floor((c % 16777216) / 262144),
   11.56  			128 + floor((c % 262144) / 4096),
   11.57  			128 + floor((c % 4096) / 64),
   11.58  			128 + c % 64)
   11.59  	else
   11.60 -		return char(252 + floor(c / 1073741824), 
   11.61 -			128 + floor((c % 1073741824) / 16777216), 
   11.62 -			128 + floor((c % 16777216) / 262144), 
   11.63 -			128 + floor((c % 262144) / 4096), 
   11.64 -			128 + floor((c % 4096) / 64), 
   11.65 +		return char(252 + floor(c / 1073741824),
   11.66 +			128 + floor((c % 1073741824) / 16777216),
   11.67 +			128 + floor((c % 16777216) / 262144),
   11.68 +			128 + floor((c % 262144) / 4096),
   11.69 +			128 + floor((c % 4096) / 64),
   11.70  			128 + c % 64)
   11.71  	end
   11.72  end
   11.73 @@ -194,7 +194,7 @@
   11.74  --	Iterate over UTF-8 character values in a string or file
   11.75  
   11.76  function utf8values(s)
   11.77 -	
   11.78 +
   11.79  	local readc
   11.80  	local i = 0
   11.81  	if type(s) == "string" then
   11.82 @@ -208,11 +208,11 @@
   11.83  			return c and c:byte(1)
   11.84  		end
   11.85  	end
   11.86 -	
   11.87 +
   11.88  	local accu = 0
   11.89  	local numa = 0
   11.90  	local min, buf
   11.91 -	
   11.92 +
   11.93  	return function()
   11.94  		local c
   11.95  		while true do
   11.96 @@ -239,7 +239,7 @@
   11.97  				accu = accu * 64 + c - 128
   11.98  				numa = numa - 1
   11.99  				if numa == 0 then
  11.100 -					if accu == 0 or accu < min or 
  11.101 +					if accu == 0 or accu < min or
  11.102  						(accu >= 55296 and accu <= 57343) then
  11.103  						break
  11.104  					end
  11.105 @@ -284,7 +284,7 @@
  11.106  
  11.107  -------------------------------------------------------------------------------
  11.108  
  11.109 ---	Copy file - 
  11.110 +--	Copy file -
  11.111  --	arguments may be filenames or open filehandles
  11.112  
  11.113  function copyfile(s, d, bufsize)
    12.1 --- a/cgi-bin/tek/os/posix.c	Mon May 28 11:24:23 2007 +0200
    12.2 +++ b/cgi-bin/tek/os/posix.c	Fri Oct 05 01:41:59 2007 +0200
    12.3 @@ -44,10 +44,10 @@
    12.4  
    12.5  	if (attr == NULL)
    12.6  		lua_newtable(L);
    12.7 -		
    12.8 +
    12.9  	setfield(L, attr, "dev", s->st_dev);
   12.10  	setfield(L, attr, "ino", s->st_ino);
   12.11 -	
   12.12 +
   12.13  	if (attr == NULL || strcmp(attr, "mode") == 0)
   12.14  	{
   12.15  		if (S_ISREG(s->st_mode))
   12.16 @@ -69,7 +69,7 @@
   12.17  		if (attr == NULL)
   12.18  			lua_setfield(L, -2, "mode");
   12.19  	}
   12.20 -	
   12.21 +
   12.22  	setfield(L, attr, "nlink", s->st_nlink);
   12.23  	setfield(L, attr, "uid", s->st_uid);
   12.24  	setfield(L, attr, "gid", s->st_gid);
   12.25 @@ -80,22 +80,22 @@
   12.26  	setfield(L, attr, "size", s->st_size);
   12.27  	setfield(L, attr, "blocks", s->st_blocks);
   12.28  	setfield(L, attr, "blksize", s->st_blksize);
   12.29 -	
   12.30 +
   12.31  	return 1;
   12.32  }
   12.33  
   12.34  
   12.35 -static int 
   12.36 +static int
   12.37  posix_stat(lua_State *L)
   12.38  {
   12.39  	const char *path = luaL_checkstring(L, 1);
   12.40  	const char *attr = luaL_optstring(L, 2, NULL);
   12.41  	struct stat s;
   12.42  	return setstat(L, stat(path, &s), &s, attr);
   12.43 -}	
   12.44 +}
   12.45  
   12.46  
   12.47 -static int 
   12.48 +static int
   12.49  posix_lstat(lua_State *L)
   12.50  {
   12.51  	const char *path = luaL_checkstring(L, 1);
   12.52 @@ -105,7 +105,7 @@
   12.53  }
   12.54  
   12.55  
   12.56 -static int 
   12.57 +static int
   12.58  posix_opendir(lua_State *L)
   12.59  {
   12.60  	const char *path = luaL_checkstring(L, 1);
   12.61 @@ -136,7 +136,7 @@
   12.62  }
   12.63  
   12.64  
   12.65 -static int 
   12.66 +static int
   12.67  posix_closedir(lua_State *L)
   12.68  {
   12.69  	DIR **pdir = getinstptr(L, 1, DIRCLASSNAME);
   12.70 @@ -146,7 +146,7 @@
   12.71  }
   12.72  
   12.73  
   12.74 -static int 
   12.75 +static int
   12.76  posix_readdir(lua_State *L)
   12.77  {
   12.78  	DIR *dir = *getinstptr(L, 1, DIRCLASSNAME);
   12.79 @@ -159,7 +159,7 @@
   12.80  }
   12.81  
   12.82  
   12.83 -static int 
   12.84 +static int
   12.85  posix_readlink(lua_State *L)
   12.86  {
   12.87  	const char *path = luaL_checkstring(L, 1);
   12.88 @@ -177,7 +177,7 @@
   12.89  }
   12.90  
   12.91  
   12.92 -static int 
   12.93 +static int
   12.94  posix_mkdir(lua_State *L)
   12.95  {
   12.96  	const char *path = luaL_checkstring(L, 1);
   12.97 @@ -192,7 +192,7 @@
   12.98  }
   12.99  
  12.100  
  12.101 -static int 
  12.102 +static int
  12.103  posix_symlink(lua_State *L)
  12.104  {
  12.105  	const char *oldpath = luaL_checkstring(L, 1);
  12.106 @@ -216,8 +216,8 @@
  12.107  	char *dp = dest;
  12.108  	int dc = 0, slc = 0, eac = 0, wc = 0;
  12.109  	int i, c;
  12.110 -	
  12.111 -	while (len--)	
  12.112 +
  12.113 +	while (len--)
  12.114  	{
  12.115  		c = *(--sp);
  12.116  		switch (c)
  12.117 @@ -241,11 +241,11 @@
  12.118  			default:
  12.119  				if (wc)
  12.120  					break;
  12.121 -			
  12.122 +
  12.123  				if (slc)
  12.124  				{
  12.125  					slc = 0;
  12.126 -					
  12.127 +
  12.128  					if (eac > 0)
  12.129  					{
  12.130  						/* resolve one eatcount */
  12.131 @@ -254,37 +254,37 @@
  12.132  						wc = 1;
  12.133  						break;
  12.134  					}
  12.135 -					
  12.136 +
  12.137  					*dp++ = '/';
  12.138  				}
  12.139 -				
  12.140 +
  12.141  				while (dc == 2 || dc == 1)
  12.142  				{
  12.143  					*dp++ = '.';
  12.144  					dc--;
  12.145  				}
  12.146  				dc = 0;
  12.147 -				
  12.148 +
  12.149  				*dp++ = c;
  12.150  				break;
  12.151  		}
  12.152  	}
  12.153 -	
  12.154 +
  12.155  	/* unresolved eatcount */
  12.156  	if (eac)
  12.157  		return 0;
  12.158 -	
  12.159 +
  12.160  	/* resolve remaining slash */
  12.161  	if (slc)
  12.162  		*dp++ = '/';
  12.163 -	
  12.164 +
  12.165  	*dp = 0;
  12.166 -	
  12.167 +
  12.168  	len = dp - dest;
  12.169  	for (i = 0; i < len / 2; ++i)
  12.170  	{
  12.171  		char t = dest[i];
  12.172 -		dest[i] = dest[len - i - 1]; 
  12.173 +		dest[i] = dest[len - i - 1];
  12.174  		dest[len - i - 1] = t;
  12.175  	}
  12.176  
  12.177 @@ -292,87 +292,100 @@
  12.178  }
  12.179  
  12.180  
  12.181 -static int 
  12.182 +static int
  12.183  posix_abspath(lua_State *L)
  12.184  {
  12.185  	const char *path = luaL_checkstring(L, 1);
  12.186  	char *pwd = NULL;
  12.187  	size_t pwdsize = 16;
  12.188 -	
  12.189 +	size_t len1, len2;
  12.190 +	char *srcpath, *dstpath;
  12.191 +
  12.192  	for (;;)
  12.193  	{
  12.194 -		char *newpwd = realloc(pwd, pwdsize);
  12.195 -		if (newpwd == NULL)
  12.196 -			break;
  12.197 -		
  12.198 -		pwd = newpwd;
  12.199 -		if (getcwd(pwd, pwdsize))
  12.200 +		if (path[0] == '/')
  12.201  		{
  12.202 -			size_t len1 = strlen(pwd);
  12.203 -			size_t len2 = strlen(path);
  12.204 -			char *srcpath = malloc(len1 + 1 + len2 + 1);
  12.205 -			char *dstpath = malloc(len1 + 1 + len2 + 1);
  12.206 -			if (srcpath && dstpath)
  12.207 +			pwd = malloc(2);
  12.208 +			if (pwd == NULL)
  12.209 +				break;
  12.210 +			pwd[0] = '/';
  12.211 +			pwd[1] = 0;
  12.212 +		}
  12.213 +		else
  12.214 +		{
  12.215 +			char *newpwd = realloc(pwd, pwdsize);
  12.216 +			if (newpwd == NULL)
  12.217 +				break;
  12.218 +			pwd = newpwd;
  12.219 +
  12.220 +			if (!getcwd(pwd, pwdsize))
  12.221  			{
  12.222 -				int res;
  12.223 -				
  12.224 -				strcpy(srcpath, pwd);
  12.225 -				free(pwd);
  12.226 -				srcpath[len1] = '/';
  12.227 -				strcpy(srcpath + len1 + 1, path);
  12.228 -				res = resolvepath(srcpath, dstpath);
  12.229 -				free(srcpath);
  12.230 -				
  12.231 -				if (res)
  12.232 +				if (errno == ERANGE)
  12.233  				{
  12.234 -					lua_pushstring(L, dstpath);
  12.235 -					free(dstpath);
  12.236 -					return 1;			
  12.237 +					pwdsize <<= 1;
  12.238 +					continue;
  12.239  				}
  12.240 -				
  12.241 -				free(dstpath);
  12.242  				lua_pushnil(L);
  12.243 -				lua_pushstring(L, "Not a valid path");
  12.244 +				lua_pushstring(L, strerror(errno));
  12.245  				return 2;
  12.246  			}
  12.247 -			
  12.248 +		}
  12.249 +
  12.250 +		len1 = strlen(pwd);
  12.251 +		len2 = strlen(path);
  12.252 +		srcpath = malloc(len1 + 1 + len2 + 1);
  12.253 +		dstpath = malloc(len1 + 1 + len2 + 1);
  12.254 +
  12.255 +		if (srcpath && dstpath)
  12.256 +		{
  12.257 +			int res;
  12.258 +
  12.259 +			strcpy(srcpath, pwd);
  12.260 +			free(pwd);
  12.261 +			srcpath[len1] = '/';
  12.262 +			strcpy(srcpath + len1 + 1, path);
  12.263 +			res = resolvepath(srcpath, dstpath);
  12.264  			free(srcpath);
  12.265 +
  12.266 +			if (res)
  12.267 +			{
  12.268 +				lua_pushstring(L, dstpath);
  12.269 +				free(dstpath);
  12.270 +				return 1;
  12.271 +			}
  12.272 +
  12.273  			free(dstpath);
  12.274 -			break;
  12.275 +			lua_pushnil(L);
  12.276 +			lua_pushstring(L, "Not a valid path");
  12.277 +			return 2;
  12.278  		}
  12.279 -		
  12.280 -		if (errno == ERANGE)
  12.281 -		{
  12.282 -			pwdsize <<= 1;
  12.283 -			continue;
  12.284 -		}
  12.285 -		
  12.286 -		lua_pushnil(L);
  12.287 -		lua_pushstring(L, strerror(errno));
  12.288 -		return 2;
  12.289 +
  12.290 +		free(srcpath);
  12.291 +		free(dstpath);
  12.292 +		break;
  12.293  	}
  12.294 -	
  12.295 +
  12.296  	free(pwd);
  12.297  	luaL_error(L, "Out of memory");
  12.298  	return 0;
  12.299  }
  12.300  
  12.301  
  12.302 -static int 
  12.303 +static int
  12.304  posix_nanosleep(lua_State *L)
  12.305  {
  12.306  	struct timespec req;
  12.307  	lua_Number d = luaL_checknumber(L, 1);
  12.308 -	
  12.309 +
  12.310  	req.tv_sec = (time_t) d;
  12.311  	req.tv_nsec = (long) ((d - req.tv_sec) * 1000000000);
  12.312 -	
  12.313 +
  12.314  	if (nanosleep(&req, NULL) == 0)
  12.315  	{
  12.316  		lua_pushboolean(L, 1);
  12.317  		return 1;
  12.318  	}
  12.319 -	
  12.320 +
  12.321  	lua_pushnil(L);
  12.322  	lua_pushstring(L, strerror(errno));
  12.323  	return 2;
  12.324 @@ -429,7 +442,7 @@
  12.325  	luaL_openlib(L, libname, functions, 2);	/* classtab, libtab */
  12.326  
  12.327  	/* adjust stack */
  12.328 -	lua_pop(L, 2);	
  12.329 +	lua_pop(L, 2);
  12.330  }
  12.331  
  12.332  
    13.1 --- a/etc/config.lua.sample	Mon May 28 11:24:23 2007 +0200
    13.2 +++ b/etc/config.lua.sample	Fri Oct 05 01:41:59 2007 +0200
    13.3 @@ -1,7 +1,7 @@
    13.4  -------------------------------------------------------------------------------
    13.5  --
    13.6  --	etc/config.lua - Loona Configuration file
    13.7 ---	
    13.8 +--
    13.9  --	Uncomment settings to change their respective defaults,
   13.10  --	which are denoted below.
   13.11  --
   13.12 @@ -22,10 +22,10 @@
   13.13  
   13.14  -- Extensions directory -------------------------------------------------------
   13.15  -- extdir = "../extensions";
   13.16 -	
   13.17 +
   13.18  -- Content directory ----------------------------------------------------------
   13.19  -- contentdir = "../content";
   13.20 -	
   13.21 +
   13.22  -- Locale directory -----------------------------------------------------------
   13.23  -- localedir = "../locale";
   13.24  
   13.25 @@ -47,3 +47,5 @@
   13.26  -- Open external links in same window -----------------------------------------
   13.27  -- extlinksamewindow = false;
   13.28  
   13.29 +-- Allow editing of published profile -----------------------------------------
   13.30 +-- editablepubprofile = false;
    14.1 --- a/etc/passwd.lua.sample	Mon May 28 11:24:23 2007 +0200
    14.2 +++ b/etc/passwd.lua.sample	Fri Oct 05 01:41:59 2007 +0200
    14.3 @@ -2,8 +2,18 @@
    14.4  --
    14.5  --	etc/passwd.lua - Loona passwords
    14.6  --
    14.7 +--	Permissions:
    14.8 +--
    14.9 +--	"e" - edit node content
   14.10 +--	"m" - edit menu structure
   14.11 +--	"a" - edit properties ("attributes")
   14.12 +--	"p" - change profile
   14.13 +--	"c" - create/copy/publish profile
   14.14 +--	"v" - hidden/secret sections are visible for user
   14.15 +--	"d" - see debug messages
   14.16 +--
   14.17  -------------------------------------------------------------------------------
   14.18  
   14.19 ---	admin = { username = "admin", password = "blafasel" },
   14.20 ---	foo = { username = "foo", password = "bar" },
   14.21 +--	admin = { username = "admin", password = "blafasel", permissions = "emapcvd" }
   14.22 +--	foo = { username = "foo", password = "bar", permisisons = "vd" },
   14.23  
    15.1 --- a/extensions/checkaccept.lua	Mon May 28 11:24:23 2007 +0200
    15.2 +++ b/extensions/checkaccept.lua	Fri Oct 05 01:41:59 2007 +0200
    15.3 @@ -22,7 +22,7 @@
    15.4  			return wdy..", "..d.."-"..mon.."-"..(y+1).." "..h..":"..m..":"..s.." GMT"
    15.5  		end)
    15.6  	cdata = cgi.encodeurl(accept)
    15.7 -	loona:setheader("Set-cookie: "..cookiename.."="..cdata.."; expires="..expdate.."; path=/;\n")
    15.8 +	loona:addheader("Set-cookie: "..cookiename.."="..cdata.."; expires="..expdate.."; path=/;\n")
    15.9  end
   15.10  
   15.11  if accept ~= "1" then%>
    16.1 --- a/htdocs/index.lua	Mon May 28 11:24:23 2007 +0200
    16.2 +++ b/htdocs/index.lua	Fri Oct 05 01:41:59 2007 +0200
    16.3 @@ -7,8 +7,8 @@
    16.4  --
    16.5  
    16.6  local os = require "os"
    16.7 -loona:setheader("Content-Type: text/html; charset=utf-8\n\n")
    16.8 -
    16.9 +loona:addheader("Content-Type: text/html; charset=utf-8\n\n")
   16.10 +local sectionname = loona.sectionpath:match("^(%w+)_?.*$")
   16.11  %>
   16.12  
   16.13  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   16.14 @@ -21,10 +21,11 @@
   16.15  		Loona CMS : <%=loona:encodeform(loona:title())%>
   16.16  	</title>
   16.17  </head>
   16.18 -<body>
   16.19 +
   16.20 +<body id="<%=sectionname%>">
   16.21  	<div id="container">
   16.22  		<div id="logo">
   16.23 -			<a href="<%=loona:href("home")%>"><img
   16.24 +			<a href="<%=loona:href(loona.config.defname)%>"><img
   16.25  				src="/images/loona.png" alt="LOona logo" /></a>
   16.26  		</div>
   16.27  		<div id="menu">
    17.1 --- a/htdocs/loona.css	Mon May 28 11:24:23 2007 +0200
    17.2 +++ b/htdocs/loona.css	Fri Oct 05 01:41:59 2007 +0200
    17.3 @@ -1,6 +1,6 @@
    17.4  
    17.5  /*
    17.6 -**	loona.css - Exemplary Loona site stylesheet
    17.7 +**	loona.css - Exemplary LOona site stylesheet
    17.8  **	Written by Timm S. Mueller <tmueller at neoscientists.org>
    17.9  **	See copyright notice in COPYRIGHT
   17.10  */
    18.1 --- a/htdocs/upload.lua	Mon May 28 11:24:23 2007 +0200
    18.2 +++ b/htdocs/upload.lua	Fri Oct 05 01:41:59 2007 +0200
    18.3 @@ -5,14 +5,14 @@
    18.4  local document = request:getdocument()
    18.5  
    18.6  if loona.args.file and loona.args.show then
    18.7 -	
    18.8 +
    18.9  	local f = loona.args.file.file:read("*a")
   18.10 -	loona:setheader("Content-Type: " .. loona.args.file["content-type"] .. "\n\n")
   18.11 +	loona:addheader("Content-Type: " .. loona.args.file["content-type"] .. "\n\n")
   18.12  	loona:out(f)
   18.13  
   18.14  else
   18.15 -	
   18.16 -	loona:setheader("Content-Type: text/html; charset=utf-8\n\n")
   18.17 +
   18.18 +	loona:addheader("Content-Type: text/html; charset=utf-8\n\n")
   18.19  %>
   18.20  	<?xml version="1.0"?>
   18.21  	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   18.22 @@ -23,7 +23,7 @@
   18.23  			</title>
   18.24  		</head>
   18.25  		<body>
   18.26 -			<form action="<%=loona.document%>" method="post" enctype="multipart/form-data">	
   18.27 +			<form action="<%=loona.document%>" method="post" enctype="multipart/form-data">
   18.28  				<fieldset>
   18.29  					<legend>
   18.30  						Please upload picture