Lua Memo

自作のCGIライブラリ。Lua の勉強を兼ねて、PHP ライクに使えるCGIライブラリを作ってみた。

kucgi.zip

以下は、そのライブラリのメイン部分。実際には、もう一つファイルが必要

file:kucgi.lua

--[[
kucgi.lua is CGI library Like PHP
@author kujirahand.com

set variables (_GET | _POST | _FILES | _SERVER)

[WARNING]
_FILES ... Could not upload Binary file because Lua's stdin is Text mode.
@see http://lua-users.org/lists/lua-l/2006-10/msg00651.html
--]]


local modname = "kucgi"
local kucgi = {}
_G[modname] = kucgi

require "ku"

-- register global like PHP
-- _SERVER
_SERVER = {}
if (_G.arg) then
	_SERVER["LUA_SELF"] = _G.arg[0]
end
do
	local env_list = {
		"REMOTE_ADDR","PATH_INFO","SCRIPT_FILENAME",
		"SCRIPT_NAME","HTTP_HOST","HTTP_USER_AGENT","OS","PATH_TRANSLATED",
		"QUERY_STRING","REMOTE_HOST","REMOTE_PORT","REQUEST_METHOD",
		"REQUEST_URI","SERVER_PORT","SERVER_PROTOCOL","SERVER_SOFTWARE"
		}
	for i,v in ipairs(env_list) do
		_SERVER[v] = os.getenv(v)
	end
end

-- _GET
--[=[= parse query _GET
@param query_string os.getenv("QUERY_STRING")
@return query table
@example
> a=kucgi.parse_query("a=1&b=2&c=3");return a["a"]..a["b"]..a["c"]
123
> a=kucgi.parse_query("a=&b=2&c=3");return a["a"]..a["b"]..a["c"]
23
> a=kucgi.parse_query("a=test&b=hoge");return a["b"]
hoge
=]=]
kucgi.parse_query = function (query_string)
	if type(query_string) ~= "string" then
		return {}
	end
	local v = {}
	query_string:gsub("([^%=]+)%=([^%&]*)&?",
		function(key,val)
			key = ku.urldecode(key)
			val = ku.urldecode(val)
			v[key] = val
		end)
	return v
end
_GET = kucgi.parse_query(_SERVER["QUERY_STRING"])

--[=[= get request method
@return GET or POST
=]=]
kucgi.method = os.getenv("REQUEST_METHOD")
do
	if not kucgi.method then kucgi.method = "GET" end
	kucgi.method = string.upper(kucgi.method)
end

--[=[= parse multipart/form header
> a=kucgi.parse_header([[Content-Disposition: form-data; name="hoge2"]])
> =a["Content-Disposition"]
form-data; name="hoge2"
> =a["name"]
hoge2
> a=kucgi.parse_header([[Content-Disposition: form-data; name="a"; filename="b" ]])
> =a["filename"]
b
=]=]
kucgi.parse_header = function (head, EOL)
	if EOL == nil then EOL = "\r\n" end
	local res = {}
	local lines = ku.split(head, EOL)
	for i, line in ipairs(lines) do
		local key, val = line:match("([^%:]+)%:(.+)$")
		if key and val then
			key = ku.trim(key)
			val = ku.trim(val)
			res[key] = val
		end
	end
	-- other parameter
	if res["Content-Disposition"] then
		local tok = ku.Tokenizer:new(res["Content-Disposition"])
		while tok.src ~= "" do
			local line = tok:getToken(";")
			local key, val = ku.splitToken(line, "=")
			key = ku.trim(key)
			val = ku.trim(val)
			if type(val) == "string" then
				if (val:sub(1,1) == '"' and val:sub(-1) == '"') or
				   (val:sub(1,1) == "'" and val:sub(-1) == "'")
				then
					val = string.sub(val, 2, val:len()-1)
				end
				res[key] = val
			end
		end
	end
	return res
end

-- _POST and _FILES
--[=[= parse post method
@param content_type
@param contents
@return _POST, _FILES
> c = "\r\n--hoge\r\nContent-Disposition: form-data; name=\"hoge1\"\r\n\r\ntest\r\n--hoge--"
> a = kucgi.parse_post("multipart/form-data;boundary=hoge",c)
> =a["hoge1"]
test

> c = "\r\n--hoge\r\nContent-Disposition: form-data; name=\"hoge1\"; filename=\"a.txt\"\r\n\r\ntest\r\n--hoge--"
> a,b = kucgi.parse_post("multipart/form-data;boundary=hoge",c)
> =b["hoge1"]["body"]
test
=]=]
kucgi.parse_post = function (content_type, contents)
	-- print("<pre>"..contents.."</pre>")
	-- check x-www-form-urlencoded
	if content_type == "application/x-www-form-urlencoded" then
		return kucgi.parse_query(contents), {}
	end
	-- check multipart/form
	local post  = {}
	local files = {}
	local type1 = string.match(content_type, "^[^%;]+")
	if (type1 == "multipart/form-data") then
		-- multipart
		local boundary = string.match(content_type,"boundary=(.+)%S*")
		if not boundary then return {}, {} end
		local EOL = "\r\n"
		local sep = "--"..boundary
		contents = ku.trim(contents)
		local s1, s2 = ku.splitToken(contents, sep)
		s2 = s2 or " "
		if s2:sub(1,1) == "\n" then EOL = "\n" else EOL = "\r\n" end
		parts = ku.Tokenizer:new(contents)
		while parts.src ~= "" do
			local pat = parts:getToken(sep)
			pat = ku.trim(pat)
			if pat == "--" then break end
			if pat ~= "" then
				local head, body = ku.splitToken(pat, EOL..EOL)
				local heads = kucgi.parse_header(head, EOL)
				if heads["filename"] then
					if heads["filename"] ~= "" then
						files[ heads["name"] ] = {
							name = heads["filename"],
							size = string.len(body),
							["body"] = body
						}
					end
				else
					if heads["name"] then
						post[heads["name"]] = body
					end
				end
			end
		end
	end
	return post, files
end

_POST  = {}
_FILES = {}
if kucgi.method == "POST" then
	local content_type 	= os.getenv("CONTENT_TYPE")
	local len_str 		= os.getenv("CONTENT_LENGTH")
	if len_str then
		local len = tonumber(len_str)
		local src = io.read(len)
		-- todo: "io.read()" is text mode
		fp = io.open("test.txt","w")
		fp:write(src)
		_POST, _FILES = kucgi.parse_post(content_type, src)
	end
end

-- shortcut
echo  = io.write