GSoC Commit #1: LuCId + HTTP-Server
This commit is contained in:
parent
0ad58e38b4
commit
8c4f847ea5
20 changed files with 1739 additions and 10 deletions
5
Makefile
5
Makefile
|
@ -52,10 +52,11 @@ runboa: hostenv
|
|||
runhttpd: hostenv
|
||||
build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) "$(realpath host/usr/bin/lucittpd) $(realpath host)/usr/lib/lucittpd/plugins"
|
||||
|
||||
runluci: runhttpd
|
||||
runluci: luahost
|
||||
build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) "$(realpath libs/httpd/host/runluci) $(realpath host) $(HTDOCS)"
|
||||
|
||||
runlua: hostenv
|
||||
build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) lua
|
||||
build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) "lua -i build/setup.lua"
|
||||
|
||||
runshell: hostenv
|
||||
build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) $$SHELL
|
||||
|
|
|
@ -38,9 +38,6 @@ luastrip: luasource
|
|||
luacompile: luasource
|
||||
for i in $$(find dist -name *.lua -not -name debug.lua); do $(LUAC) $(LUAC_OPTIONS) -o $$i $$i; done
|
||||
|
||||
luagzip: luacompile
|
||||
for i in $$(find dist -name *.lua -not -name debug.lua); do gzip -f9 $$i; done
|
||||
|
||||
luaclean:
|
||||
rm -rf dist
|
||||
|
||||
|
|
11
build/setup.lua
Normal file
11
build/setup.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
local SYSROOT = os.getenv("LUCI_SYSROOT")
|
||||
require "uci"
|
||||
require "luci.model.uci".cursor = function(config, save)
|
||||
return uci.cursor(config or SYSROOT .. "/etc/config", save or SYSROOT .. "/tmp/.uci")
|
||||
end
|
||||
|
||||
local x = require "luci.uvl".UVL.__init__
|
||||
require "luci.uvl".UVL.__init__ = function(self, schemedir)
|
||||
x(self, schemedir or SYSROOT .. "/lib/uci/schema")
|
||||
end
|
||||
|
2
libs/lucid-http/Makefile
Normal file
2
libs/lucid-http/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
include ../../build/module.mk
|
||||
include ../../build/config.mk
|
33
libs/lucid-http/luasrc/lucid/http.lua
Normal file
33
libs/lucid-http/luasrc/lucid/http.lua
Normal file
|
@ -0,0 +1,33 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
|
||||
Copyright 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local require, ipairs, pcall = require, ipairs, pcall
|
||||
local srv = require "luci.lucid.http.server"
|
||||
|
||||
module "luci.lucid.http"
|
||||
|
||||
function factory(publisher)
|
||||
local server = srv.Server()
|
||||
for _, r in ipairs(publisher) do
|
||||
local t = r[".type"]
|
||||
local s, mod = pcall(require, "luci.lucid.http." .. (r[".type"] or ""))
|
||||
if s and mod then
|
||||
mod.factory(server, r)
|
||||
else
|
||||
return nil, mod
|
||||
end
|
||||
end
|
||||
|
||||
return function(...) return server:process(...) end
|
||||
end
|
47
libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua
Normal file
47
libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua
Normal file
|
@ -0,0 +1,47 @@
|
|||
--[[
|
||||
LuCId HTTP-Slave
|
||||
(c) 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local ipairs, require, tostring, type = ipairs, require, tostring, type
|
||||
local file = require "luci.lucid.http.handler.file"
|
||||
local srv = require "luci.lucid.http.server"
|
||||
|
||||
module "luci.lucid.http.DirectoryPublisher"
|
||||
|
||||
|
||||
function factory(server, config)
|
||||
config.domain = config.domain or ""
|
||||
local vhost = server:get_vhosts()[config.domain]
|
||||
if not vhost then
|
||||
vhost = srv.VHost()
|
||||
server:set_vhost(config.domain, vhost)
|
||||
end
|
||||
|
||||
local handler = file.Simple(config.name, config.physical, config)
|
||||
if config.read then
|
||||
for _, r in ipairs(config.read) do
|
||||
if r:sub(1,1) == ":" then
|
||||
handler:restrict({interface = r:sub(2)})
|
||||
else
|
||||
handler:restrict({user = r})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(config.virtual) == "table" then
|
||||
for _, v in ipairs(config.virtual) do
|
||||
vhost:set_handler(v, handler)
|
||||
end
|
||||
else
|
||||
vhost:set_handler(config.virtual, handler)
|
||||
end
|
||||
end
|
62
libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua
Normal file
62
libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua
Normal file
|
@ -0,0 +1,62 @@
|
|||
--[[
|
||||
LuCId HTTP-Slave
|
||||
(c) 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local ipairs, pcall, type = ipairs, pcall, type
|
||||
local luci = require "luci.lucid.http.handler.luci"
|
||||
local srv = require "luci.lucid.http.server"
|
||||
|
||||
|
||||
module "luci.lucid.http.LuciWebPublisher"
|
||||
|
||||
function factory(server, config)
|
||||
pcall(function()
|
||||
require "luci.dispatcher"
|
||||
require "luci.cbi"
|
||||
end)
|
||||
|
||||
config.domain = config.domain or ""
|
||||
local vhost = server:get_vhosts()[config.domain]
|
||||
if not vhost then
|
||||
vhost = srv.VHost()
|
||||
server:set_vhost(config.domain, vhost)
|
||||
end
|
||||
|
||||
local prefix
|
||||
if config.physical and #config.physical > 0 then
|
||||
prefix = {}
|
||||
for k in config.physical:gmatch("[^/]+") do
|
||||
if #k > 0 then
|
||||
prefix[#prefix+1] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local handler = luci.Luci(config.name, prefix)
|
||||
if config.exec then
|
||||
for _, r in ipairs(config.exec) do
|
||||
if r:sub(1,1) == ":" then
|
||||
handler:restrict({interface = r:sub(2)})
|
||||
else
|
||||
handler:restrict({user = r})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(config.virtual) == "table" then
|
||||
for _, v in ipairs(config.virtual) do
|
||||
vhost:set_handler(v, handler)
|
||||
end
|
||||
else
|
||||
vhost:set_handler(config.virtual, handler)
|
||||
end
|
||||
end
|
31
libs/lucid-http/luasrc/lucid/http/Redirector.lua
Normal file
31
libs/lucid-http/luasrc/lucid/http/Redirector.lua
Normal file
|
@ -0,0 +1,31 @@
|
|||
--[[
|
||||
LuCId HTTP-Slave
|
||||
(c) 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local ipairs = ipairs
|
||||
local catchall = require "luci.lucid.http.handler.catchall"
|
||||
local srv = require "luci.lucid.http.server"
|
||||
|
||||
module "luci.lucid.http.Redirector"
|
||||
|
||||
|
||||
function factory(server, config)
|
||||
config.domain = config.domain or ""
|
||||
local vhost = server:get_vhosts()[config.domain]
|
||||
if not vhost then
|
||||
vhost = srv.VHost()
|
||||
server:set_vhost(config.domain, vhost)
|
||||
end
|
||||
|
||||
local handler = catchall.Redirect(config.name, config.physical)
|
||||
vhost:set_handler(config.virtual, handler)
|
||||
end
|
53
libs/lucid-http/luasrc/lucid/http/handler/catchall.lua
Normal file
53
libs/lucid-http/luasrc/lucid/http/handler/catchall.lua
Normal file
|
@ -0,0 +1,53 @@
|
|||
--[[
|
||||
LuCId HTTP-Slave
|
||||
(c) 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local srv = require "luci.lucid.http.server"
|
||||
local proto = require "luci.http.protocol"
|
||||
|
||||
module "luci.lucid.http.handler.catchall"
|
||||
|
||||
Redirect = util.class(srv.Handler)
|
||||
|
||||
function Redirect.__init__(self, name, target)
|
||||
srv.Handler.__init__(self, name)
|
||||
self.target = target
|
||||
end
|
||||
|
||||
function Redirect.handle_GET(self, request)
|
||||
local target = self.target
|
||||
local protocol = request.env.HTTPS and "https://" or "http://"
|
||||
local server = request.env.SERVER_ADDR
|
||||
if server:find(":") then
|
||||
server = "[" .. server .. "]"
|
||||
end
|
||||
|
||||
if self.target:sub(1,1) == ":" then
|
||||
target = protocol .. server .. target
|
||||
end
|
||||
|
||||
local s, e = target:find("%TARGET%", 1, true)
|
||||
if s then
|
||||
local req = protocol .. (request.env.HTTP_HOST or server)
|
||||
.. request.env.REQUEST_URI
|
||||
target = target:sub(1, s-1) .. req .. target:sub(e+1)
|
||||
end
|
||||
|
||||
return 302, { Location = target }
|
||||
end
|
||||
|
||||
Redirect.handle_POST = Redirect.handle_GET
|
||||
|
||||
function Redirect.handle_HEAD(self, request)
|
||||
local stat, head = self:handle_GET(request)
|
||||
return stat, head
|
||||
end
|
250
libs/lucid-http/luasrc/lucid/http/handler/file.lua
Normal file
250
libs/lucid-http/luasrc/lucid/http/handler/file.lua
Normal file
|
@ -0,0 +1,250 @@
|
|||
--[[
|
||||
|
||||
HTTP server implementation for LuCI - file handler
|
||||
(c) 2008 Steven Barth <steven@midlink.org>
|
||||
(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
|
||||
]]--
|
||||
|
||||
local ipairs, type, tonumber = ipairs, type, tonumber
|
||||
local os = require "os"
|
||||
local nixio = require "nixio", require "nixio.util"
|
||||
local fs = require "nixio.fs"
|
||||
local util = require "luci.util"
|
||||
local ltn12 = require "luci.ltn12"
|
||||
local srv = require "luci.lucid.http.server"
|
||||
local string = require "string"
|
||||
|
||||
local prot = require "luci.http.protocol"
|
||||
local date = require "luci.http.protocol.date"
|
||||
local mime = require "luci.http.protocol.mime"
|
||||
local cond = require "luci.http.protocol.conditionals"
|
||||
|
||||
module "luci.lucid.http.handler.file"
|
||||
|
||||
Simple = util.class(srv.Handler)
|
||||
|
||||
function Simple.__init__(self, name, docroot, options)
|
||||
srv.Handler.__init__(self, name)
|
||||
self.docroot = docroot
|
||||
self.realdocroot = fs.realpath(self.docroot)
|
||||
|
||||
options = options or {}
|
||||
self.dirlist = not options.noindex
|
||||
self.error404 = options.error404
|
||||
end
|
||||
|
||||
function Simple.parse_range(self, request, size)
|
||||
if not request.headers.Range then
|
||||
return true
|
||||
end
|
||||
|
||||
local from, to = request.headers.Range:match("bytes=([0-9]*)-([0-9]*)")
|
||||
if not (from or to) then
|
||||
return true
|
||||
end
|
||||
|
||||
from, to = tonumber(from), tonumber(to)
|
||||
if not (from or to) then
|
||||
return true
|
||||
elseif not from then
|
||||
from, to = size - to, size - 1
|
||||
elseif not to then
|
||||
to = size - 1
|
||||
end
|
||||
|
||||
-- Not satisfiable
|
||||
if from >= size then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Normalize
|
||||
if to >= size then
|
||||
to = size - 1
|
||||
end
|
||||
|
||||
local range = "bytes " .. from .. "-" .. to .. "/" .. size
|
||||
return from, (1 + to - from), range
|
||||
end
|
||||
|
||||
function Simple.getfile(self, uri)
|
||||
if not self.realdocroot then
|
||||
self.realdocroot = fs.realpath(self.docroot)
|
||||
end
|
||||
local file = fs.realpath(self.docroot .. uri)
|
||||
if not file or file:sub(1, #self.realdocroot) ~= self.realdocroot then
|
||||
return uri
|
||||
end
|
||||
return file, fs.stat(file)
|
||||
end
|
||||
|
||||
function Simple.handle_GET(self, request)
|
||||
local file, stat = self:getfile(prot.urldecode(request.env.PATH_INFO, true))
|
||||
|
||||
if stat then
|
||||
if stat.type == "reg" then
|
||||
|
||||
-- Generate Entity Tag
|
||||
local etag = cond.mk_etag( stat )
|
||||
|
||||
-- Check conditionals
|
||||
local ok, code, hdrs
|
||||
|
||||
ok, code, hdrs = cond.if_modified_since( request, stat )
|
||||
if ok then
|
||||
ok, code, hdrs = cond.if_match( request, stat )
|
||||
if ok then
|
||||
ok, code, hdrs = cond.if_unmodified_since( request, stat )
|
||||
if ok then
|
||||
ok, code, hdrs = cond.if_none_match( request, stat )
|
||||
if ok then
|
||||
local f, err = nixio.open(file)
|
||||
|
||||
if f then
|
||||
local code = 200
|
||||
local o, s, r = self:parse_range(request, stat.size)
|
||||
|
||||
if not o then
|
||||
return self:failure(416, "Invalid Range")
|
||||
end
|
||||
|
||||
local headers = {
|
||||
["Last-Modified"] = date.to_http( stat.mtime ),
|
||||
["Content-Type"] = mime.to_mime( file ),
|
||||
["ETag"] = etag,
|
||||
["Accept-Ranges"] = "bytes",
|
||||
}
|
||||
|
||||
if o == true then
|
||||
s = stat.size
|
||||
else
|
||||
code = 206
|
||||
headers["Content-Range"] = r
|
||||
f:seek(o)
|
||||
end
|
||||
|
||||
headers["Content-Length"] = s
|
||||
|
||||
-- Send Response
|
||||
return code, headers, srv.IOResource(f, s)
|
||||
else
|
||||
return self:failure( 403, err:gsub("^.+: ", "") )
|
||||
end
|
||||
else
|
||||
return code, hdrs
|
||||
end
|
||||
else
|
||||
return code, hdrs
|
||||
end
|
||||
else
|
||||
return code, hdrs
|
||||
end
|
||||
else
|
||||
return code, hdrs
|
||||
end
|
||||
|
||||
elseif stat.type == "dir" then
|
||||
|
||||
local ruri = request.env.REQUEST_URI:gsub("/$", "")
|
||||
local duri = prot.urldecode( ruri, true )
|
||||
local root = self.docroot
|
||||
|
||||
-- check for index files
|
||||
local index_candidates = {
|
||||
"index.html", "index.htm", "default.html", "default.htm",
|
||||
"index.txt", "default.txt"
|
||||
}
|
||||
|
||||
-- try to find an index file and redirect to it
|
||||
for i, candidate in ipairs( index_candidates ) do
|
||||
local istat = fs.stat(
|
||||
root .. "/" .. duri .. "/" .. candidate
|
||||
)
|
||||
|
||||
if istat ~= nil and istat.type == "reg" then
|
||||
return 302, { Location = ruri .. "/" .. candidate }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local html = string.format(
|
||||
'<?xml version="1.0" encoding="utf-8"?>\n' ..
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' ..
|
||||
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'..
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml" ' ..
|
||||
'xml:lang="en" lang="en">\n' ..
|
||||
'<head>\n' ..
|
||||
'<title>Index of %s/</title>\n' ..
|
||||
'<style type="text/css">\n' ..
|
||||
'body { color:#000000 } ' ..
|
||||
'li { border-bottom:1px dotted #CCCCCC; padding:3px } ' ..
|
||||
'small { font-size:60%%; color:#333333 } ' ..
|
||||
'p { margin:0 }' ..
|
||||
'\n</style></head><body><h1>Index of %s/</h1><hr /><ul>'..
|
||||
'<li><p><a href="%s/../">../</a> ' ..
|
||||
'<small>(parent directory)</small><br />' ..
|
||||
'<small></small></li>',
|
||||
duri, duri, ruri
|
||||
)
|
||||
|
||||
local entries = fs.dir( file )
|
||||
|
||||
if type(entries) == "function" then
|
||||
for i, e in util.vspairs(nixio.util.consume(entries)) do
|
||||
local estat = fs.stat( file .. "/" .. e )
|
||||
|
||||
if estat.type == "dir" then
|
||||
html = html .. string.format(
|
||||
'<li><p><a href="%s/%s/">%s/</a> ' ..
|
||||
'<small>(directory)</small><br />' ..
|
||||
'<small>Changed: %s</small></li>',
|
||||
ruri, prot.urlencode( e ), e,
|
||||
date.to_http( estat.mtime )
|
||||
)
|
||||
else
|
||||
html = html .. string.format(
|
||||
'<li><p><a href="%s/%s">%s</a> ' ..
|
||||
'<small>(%s)</small><br />' ..
|
||||
'<small>Size: %i Bytes | ' ..
|
||||
'Changed: %s</small></li>',
|
||||
ruri, prot.urlencode( e ), e,
|
||||
mime.to_mime( e ),
|
||||
estat.size, date.to_http( estat.mtime )
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
html = html .. '</ul><hr /><address>LuCId-HTTPd' ..
|
||||
'</address></body></html>'
|
||||
|
||||
return 200, {
|
||||
["Date"] = date.to_http( os.time() );
|
||||
["Content-Type"] = "text/html; charset=utf-8";
|
||||
}, ltn12.source.string(html)
|
||||
else
|
||||
return self:failure(403, "Permission denied")
|
||||
end
|
||||
else
|
||||
return self:failure(403, "Unable to transmit " .. stat.type .. " " .. file)
|
||||
end
|
||||
else
|
||||
if self.error404 then
|
||||
return 302, { Location = self.error404 }
|
||||
else
|
||||
return self:failure(404, "No such file: " .. file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Simple.handle_HEAD(self, ...)
|
||||
local stat, head = self:handle_GET(...)
|
||||
return stat, head
|
||||
end
|
96
libs/lucid-http/luasrc/lucid/http/handler/luci.lua
Normal file
96
libs/lucid-http/luasrc/lucid/http/handler/luci.lua
Normal file
|
@ -0,0 +1,96 @@
|
|||
--[[
|
||||
LuCId HTTP-Slave
|
||||
(c) 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local dsp = require "luci.dispatcher"
|
||||
local util = require "luci.util"
|
||||
local http = require "luci.http"
|
||||
local ltn12 = require "luci.ltn12"
|
||||
local srv = require "luci.lucid.http.server"
|
||||
local coroutine = require "coroutine"
|
||||
local type = type
|
||||
|
||||
module "luci.lucid.http.handler.luci"
|
||||
|
||||
Luci = util.class(srv.Handler)
|
||||
|
||||
function Luci.__init__(self, name, prefix)
|
||||
srv.Handler.__init__(self, name)
|
||||
self.prefix = prefix
|
||||
end
|
||||
|
||||
function Luci.handle_HEAD(self, ...)
|
||||
local stat, head = self:handle_GET(...)
|
||||
return stat, head
|
||||
end
|
||||
|
||||
function Luci.handle_POST(self, ...)
|
||||
return self:handle_GET(...)
|
||||
end
|
||||
|
||||
function Luci.handle_GET(self, request, sourcein)
|
||||
local r = http.Request(
|
||||
request.env,
|
||||
sourcein
|
||||
)
|
||||
|
||||
local res, id, data1, data2 = true, 0, nil, nil
|
||||
local headers = {}
|
||||
local status = 200
|
||||
local active = true
|
||||
|
||||
local x = coroutine.create(dsp.httpdispatch)
|
||||
while not id or id < 3 do
|
||||
res, id, data1, data2 = coroutine.resume(x, r, self.prefix)
|
||||
|
||||
if not res then
|
||||
status = 500
|
||||
headers["Content-Type"] = "text/plain"
|
||||
return status, headers, ltn12.source.string(id)
|
||||
end
|
||||
|
||||
if id == 1 then
|
||||
status = data1
|
||||
elseif id == 2 then
|
||||
if not headers[data1] then
|
||||
headers[data1] = data2
|
||||
elseif type(headers[data1]) ~= "table" then
|
||||
headers[data1] = {headers[data1], data2}
|
||||
else
|
||||
headers[data1][#headers[data1]+1] = data2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if id == 6 then
|
||||
while (coroutine.resume(x)) do end
|
||||
return status, headers, srv.IOResource(data1, data2)
|
||||
end
|
||||
|
||||
local function iter()
|
||||
local res, id, data = coroutine.resume(x)
|
||||
if not res then
|
||||
return nil, id
|
||||
elseif not id or not active then
|
||||
return true
|
||||
elseif id == 5 then
|
||||
active = false
|
||||
while (coroutine.resume(x)) do end
|
||||
return nil
|
||||
elseif id == 4 then
|
||||
return data
|
||||
end
|
||||
end
|
||||
|
||||
return status, headers, iter
|
||||
end
|
||||
|
522
libs/lucid-http/luasrc/lucid/http/server.lua
Normal file
522
libs/lucid-http/luasrc/lucid/http/server.lua
Normal file
|
@ -0,0 +1,522 @@
|
|||
--[[
|
||||
LuCId HTTP-Slave
|
||||
(c) 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local ipairs, pairs = ipairs, pairs
|
||||
local tostring, tonumber = tostring, tonumber
|
||||
local pcall, assert, type = pcall, assert, type
|
||||
|
||||
local os = require "os"
|
||||
local nixio = require "nixio"
|
||||
local util = require "luci.util"
|
||||
local ltn12 = require "luci.ltn12"
|
||||
local proto = require "luci.http.protocol"
|
||||
local table = require "table"
|
||||
local date = require "luci.http.protocol.date"
|
||||
|
||||
module "luci.lucid.http.server"
|
||||
|
||||
VERSION = "1.0"
|
||||
|
||||
statusmsg = {
|
||||
[200] = "OK",
|
||||
[206] = "Partial Content",
|
||||
[301] = "Moved Permanently",
|
||||
[302] = "Found",
|
||||
[304] = "Not Modified",
|
||||
[400] = "Bad Request",
|
||||
[401] = "Unauthorized",
|
||||
[403] = "Forbidden",
|
||||
[404] = "Not Found",
|
||||
[405] = "Method Not Allowed",
|
||||
[408] = "Request Time-out",
|
||||
[411] = "Length Required",
|
||||
[412] = "Precondition Failed",
|
||||
[416] = "Requested range not satisfiable",
|
||||
[500] = "Internal Server Error",
|
||||
[503] = "Server Unavailable",
|
||||
}
|
||||
|
||||
-- File Resource
|
||||
IOResource = util.class()
|
||||
|
||||
function IOResource.__init__(self, fd, len)
|
||||
self.fd, self.len = fd, len
|
||||
end
|
||||
|
||||
|
||||
-- Server handler implementation
|
||||
Handler = util.class()
|
||||
|
||||
function Handler.__init__(self, name)
|
||||
self.name = name or tostring(self)
|
||||
end
|
||||
|
||||
-- Creates a failure reply
|
||||
function Handler.failure(self, code, msg)
|
||||
return code, { ["Content-Type"] = "text/plain" }, ltn12.source.string(msg)
|
||||
end
|
||||
|
||||
-- Access Restrictions
|
||||
function Handler.restrict(self, restriction)
|
||||
if not self.restrictions then
|
||||
self.restrictions = {restriction}
|
||||
else
|
||||
self.restrictions[#self.restrictions+1] = restriction
|
||||
end
|
||||
end
|
||||
|
||||
-- Check restrictions
|
||||
function Handler.checkrestricted(self, request)
|
||||
if not self.restrictions then
|
||||
return
|
||||
end
|
||||
|
||||
local localif, user, pass
|
||||
|
||||
for _, r in ipairs(self.restrictions) do
|
||||
local stat = true
|
||||
if stat and r.interface then -- Interface restriction
|
||||
if not localif then
|
||||
for _, v in ipairs(request.server.interfaces) do
|
||||
if v.addr == request.env.SERVER_ADDR then
|
||||
localif = v.name
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if r.interface ~= localif then
|
||||
stat = false
|
||||
end
|
||||
end
|
||||
|
||||
if stat and r.user then -- User restriction
|
||||
local rh, pwe
|
||||
if not user then
|
||||
rh = (request.headers.Authorization or ""):match("Basic (.*)")
|
||||
rh = rh and nixio.bin.b64decode(rh) or ""
|
||||
user, pass = rh:match("(.*):(.*)")
|
||||
pass = pass or ""
|
||||
end
|
||||
pwe = nixio.getsp and nixio.getsp(r.user) or nixio.getpw(r.user)
|
||||
local pwh = (user == r.user) and pwe and (pwe.pwdp or pwe.passwd)
|
||||
if not pwh or #pwh < 1 or nixio.crypt(pass, pwh) ~= pwh then
|
||||
stat = false
|
||||
end
|
||||
end
|
||||
|
||||
if stat then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return 401, {
|
||||
["WWW-Authenticate"] = ('Basic realm=%q'):format(self.name),
|
||||
["Content-Type"] = 'text/plain'
|
||||
}, ltn12.source.string("Unauthorized")
|
||||
end
|
||||
|
||||
-- Processes a request
|
||||
function Handler.process(self, request, sourcein)
|
||||
local stat, code, hdr, sourceout
|
||||
|
||||
local stat, code, msg = self:checkrestricted(request)
|
||||
if stat then -- Access Denied
|
||||
return stat, code, msg
|
||||
end
|
||||
|
||||
-- Detect request Method
|
||||
local hname = "handle_" .. request.env.REQUEST_METHOD
|
||||
if self[hname] then
|
||||
-- Run the handler
|
||||
stat, code, hdr, sourceout = pcall(self[hname], self, request, sourcein)
|
||||
|
||||
-- Check for any errors
|
||||
if not stat then
|
||||
return self:failure(500, code)
|
||||
end
|
||||
else
|
||||
return self:failure(405, statusmsg[405])
|
||||
end
|
||||
|
||||
return code, hdr, sourceout
|
||||
end
|
||||
|
||||
|
||||
VHost = util.class()
|
||||
|
||||
function VHost.__init__(self)
|
||||
self.handlers = {}
|
||||
end
|
||||
|
||||
function VHost.process(self, request, ...)
|
||||
local handler
|
||||
local hlen = -1
|
||||
local uri = request.env.SCRIPT_NAME
|
||||
local sc = ("/"):byte()
|
||||
|
||||
-- SCRIPT_NAME
|
||||
request.env.SCRIPT_NAME = ""
|
||||
|
||||
-- Call URI part
|
||||
request.env.PATH_INFO = uri
|
||||
|
||||
for k, h in pairs(self.handlers) do
|
||||
if #k > hlen then
|
||||
if uri == k or (uri:sub(1, #k) == k and uri:byte(#k+1) == sc) then
|
||||
handler = h
|
||||
hlen = #k
|
||||
request.env.SCRIPT_NAME = k
|
||||
request.env.PATH_INFO = uri:sub(#k+1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if handler then
|
||||
return handler:process(request, ...)
|
||||
else
|
||||
return 404, nil, ltn12.source.string("No such handler")
|
||||
end
|
||||
end
|
||||
|
||||
function VHost.get_handlers(self)
|
||||
return self.handlers
|
||||
end
|
||||
|
||||
function VHost.set_handler(self, match, handler)
|
||||
self.handlers[match] = handler
|
||||
end
|
||||
|
||||
|
||||
local function remapipv6(adr)
|
||||
local map = "::ffff:"
|
||||
if adr:sub(1, #map) == map then
|
||||
return adr:sub(#map+1)
|
||||
else
|
||||
return adr
|
||||
end
|
||||
end
|
||||
|
||||
local function chunksource(sock, buffer)
|
||||
buffer = buffer or ""
|
||||
return function()
|
||||
local output
|
||||
local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
|
||||
while not count and #buffer <= 1024 do
|
||||
local newblock, code = sock:recv(1024 - #buffer)
|
||||
if not newblock then
|
||||
return nil, code
|
||||
end
|
||||
buffer = buffer .. newblock
|
||||
_, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
|
||||
end
|
||||
count = tonumber(count, 16)
|
||||
if not count then
|
||||
return nil, -1, "invalid encoding"
|
||||
elseif count == 0 then
|
||||
return nil
|
||||
elseif count + 2 <= #buffer - endp then
|
||||
output = buffer:sub(endp+1, endp+count)
|
||||
buffer = buffer:sub(endp+count+3)
|
||||
return output
|
||||
else
|
||||
output = buffer:sub(endp+1, endp+count)
|
||||
buffer = ""
|
||||
if count - #output > 0 then
|
||||
local remain, code = sock:recvall(count-#output)
|
||||
if not remain then
|
||||
return nil, code
|
||||
end
|
||||
output = output .. remain
|
||||
count, code = sock:recvall(2)
|
||||
else
|
||||
count, code = sock:recvall(count+2-#buffer+endp)
|
||||
end
|
||||
if not count then
|
||||
return nil, code
|
||||
end
|
||||
return output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function chunksink(sock)
|
||||
return function(chunk, err)
|
||||
if not chunk then
|
||||
return sock:writeall("0\r\n\r\n")
|
||||
else
|
||||
return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, chunk))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Server = util.class()
|
||||
|
||||
function Server.__init__(self)
|
||||
self.vhosts = {}
|
||||
end
|
||||
|
||||
function Server.get_vhosts(self)
|
||||
return self.vhosts
|
||||
end
|
||||
|
||||
function Server.set_vhost(self, name, vhost)
|
||||
self.vhosts[name] = vhost
|
||||
end
|
||||
|
||||
function Server.error(self, client, code, msg)
|
||||
hcode = tostring(code)
|
||||
|
||||
client:writeall( "HTTP/1.0 " .. hcode .. " " ..
|
||||
statusmsg[code] .. "\r\n" )
|
||||
client:writeall( "Connection: close\r\n" )
|
||||
client:writeall( "Content-Type: text/plain\r\n\r\n" )
|
||||
|
||||
if msg then
|
||||
client:writeall( "HTTP-Error " .. code .. ": " .. msg .. "\r\n" )
|
||||
end
|
||||
|
||||
client:close()
|
||||
end
|
||||
|
||||
local hdr2env = {
|
||||
["Content-Length"] = "CONTENT_LENGTH",
|
||||
["Content-Type"] = "CONTENT_TYPE",
|
||||
["Content-type"] = "CONTENT_TYPE",
|
||||
["Accept"] = "HTTP_ACCEPT",
|
||||
["Accept-Charset"] = "HTTP_ACCEPT_CHARSET",
|
||||
["Accept-Encoding"] = "HTTP_ACCEPT_ENCODING",
|
||||
["Accept-Language"] = "HTTP_ACCEPT_LANGUAGE",
|
||||
["Connection"] = "HTTP_CONNECTION",
|
||||
["Cookie"] = "HTTP_COOKIE",
|
||||
["Host"] = "HTTP_HOST",
|
||||
["Referer"] = "HTTP_REFERER",
|
||||
["User-Agent"] = "HTTP_USER_AGENT"
|
||||
}
|
||||
|
||||
function Server.parse_headers(self, source)
|
||||
local env = {}
|
||||
local req = {env = env, headers = {}}
|
||||
local line, err
|
||||
|
||||
repeat -- Ignore empty lines
|
||||
line, err = source()
|
||||
if not line then
|
||||
return nil, err
|
||||
end
|
||||
until #line > 0
|
||||
|
||||
env.REQUEST_METHOD, env.REQUEST_URI, env.SERVER_PROTOCOL =
|
||||
line:match("^([A-Z]+) ([^ ]+) (HTTP/1%.[01])$")
|
||||
|
||||
if not env.REQUEST_METHOD then
|
||||
return nil, "invalid magic"
|
||||
end
|
||||
|
||||
local key, envkey, val
|
||||
repeat
|
||||
line, err = source()
|
||||
if not line then
|
||||
return nil, err
|
||||
elseif #line > 0 then
|
||||
key, val = line:match("^([%w-]+)%s?:%s?(.*)")
|
||||
if key then
|
||||
req.headers[key] = val
|
||||
envkey = hdr2env[key]
|
||||
if envkey then
|
||||
env[envkey] = val
|
||||
end
|
||||
else
|
||||
return nil, "invalid header line"
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
until false
|
||||
|
||||
env.SCRIPT_NAME, env.QUERY_STRING = env.REQUEST_URI:match("(.*)%??(.*)")
|
||||
return req
|
||||
end
|
||||
|
||||
|
||||
function Server.process(self, client, env)
|
||||
local sourcein = function() end
|
||||
local sourcehdr = client:linesource()
|
||||
local sinkout
|
||||
local buffer
|
||||
|
||||
local close = false
|
||||
local stat, code, msg, message, err
|
||||
|
||||
client:setsockopt("socket", "rcvtimeo", 15)
|
||||
client:setsockopt("socket", "sndtimeo", 15)
|
||||
|
||||
repeat
|
||||
-- parse headers
|
||||
message, err = self:parse_headers(sourcehdr)
|
||||
|
||||
-- any other error
|
||||
if not message or err then
|
||||
if err == 11 then -- EAGAIN
|
||||
break
|
||||
else
|
||||
return self:error(client, 400, err)
|
||||
end
|
||||
end
|
||||
|
||||
-- Prepare sources and sinks
|
||||
buffer = sourcehdr(true)
|
||||
sinkout = client:sink()
|
||||
message.server = env
|
||||
|
||||
if client:is_tls_socket() then
|
||||
message.env.HTTPS = "on"
|
||||
end
|
||||
|
||||
-- Addresses
|
||||
message.env.REMOTE_ADDR = remapipv6(env.host)
|
||||
message.env.REMOTE_PORT = env.port
|
||||
|
||||
local srvaddr, srvport = client:getsockname()
|
||||
message.env.SERVER_ADDR = remapipv6(srvaddr)
|
||||
message.env.SERVER_PORT = srvport
|
||||
|
||||
-- keep-alive
|
||||
if message.env.SERVER_PROTOCOL == "HTTP/1.1" then
|
||||
close = (message.env.HTTP_CONNECTION == "close")
|
||||
else
|
||||
close = not message.env.HTTP_CONNECTION
|
||||
or message.env.HTTP_CONNECTION == "close"
|
||||
end
|
||||
|
||||
-- Uncomment this to disable keep-alive
|
||||
close = close or env.config.nokeepalive
|
||||
|
||||
if message.env.REQUEST_METHOD == "GET"
|
||||
or message.env.REQUEST_METHOD == "HEAD" then
|
||||
-- Be happy
|
||||
|
||||
elseif message.env.REQUEST_METHOD == "POST" then
|
||||
-- If we have a HTTP/1.1 client and an Expect: 100-continue header
|
||||
-- respond with HTTP 100 Continue message
|
||||
if message.env.SERVER_PROTOCOL == "HTTP/1.1"
|
||||
and message.headers.Expect == '100-continue' then
|
||||
client:writeall("HTTP/1.1 100 Continue\r\n\r\n")
|
||||
end
|
||||
|
||||
if message.headers['Transfer-Encoding'] and
|
||||
message.headers['Transfer-Encoding'] ~= "identity" then
|
||||
sourcein = chunksource(client, buffer)
|
||||
buffer = nil
|
||||
elseif message.env.CONTENT_LENGTH then
|
||||
local len = tonumber(message.env.CONTENT_LENGTH)
|
||||
if #buffer >= len then
|
||||
sourcein = ltn12.source.string(buffer:sub(1, len))
|
||||
buffer = buffer:sub(len+1)
|
||||
else
|
||||
sourcein = ltn12.source.cat(
|
||||
ltn12.source.string(buffer),
|
||||
client:blocksource(nil, len - #buffer)
|
||||
)
|
||||
end
|
||||
else
|
||||
return self:error(client, 411, statusmsg[411])
|
||||
end
|
||||
else
|
||||
return self:error(client, 405, statusmsg[405])
|
||||
end
|
||||
|
||||
|
||||
local host = self.vhosts[message.env.HTTP_HOST] or self.vhosts[""]
|
||||
if not host then
|
||||
return self:error(client, 404, "No virtual host found")
|
||||
end
|
||||
|
||||
local code, headers, sourceout = host:process(message, sourcein)
|
||||
headers = headers or {}
|
||||
|
||||
-- Post process response
|
||||
if sourceout then
|
||||
if util.instanceof(sourceout, IOResource) then
|
||||
if not headers["Content-Length"] then
|
||||
headers["Content-Length"] = sourceout.len
|
||||
end
|
||||
end
|
||||
if not headers["Content-Length"] then
|
||||
if message.http_version == 1.1 then
|
||||
headers["Transfer-Encoding"] = "chunked"
|
||||
sinkout = chunksink(client)
|
||||
else
|
||||
close = true
|
||||
end
|
||||
end
|
||||
elseif message.request_method ~= "head" then
|
||||
headers["Content-Length"] = 0
|
||||
end
|
||||
|
||||
if close then
|
||||
headers["Connection"] = "close"
|
||||
elseif message.env.SERVER_PROTOCOL == "HTTP/1.0" then
|
||||
headers["Connection"] = "Keep-Alive"
|
||||
end
|
||||
|
||||
headers["Date"] = date.to_http(os.time())
|
||||
local header = {
|
||||
message.env.SERVER_PROTOCOL .. " " .. tostring(code) .. " "
|
||||
.. statusmsg[code],
|
||||
"Server: LuCId-HTTPd/" .. VERSION
|
||||
}
|
||||
|
||||
|
||||
for k, v in pairs(headers) do
|
||||
if type(v) == "table" then
|
||||
for _, h in ipairs(v) do
|
||||
header[#header+1] = k .. ": " .. h
|
||||
end
|
||||
else
|
||||
header[#header+1] = k .. ": " .. v
|
||||
end
|
||||
end
|
||||
|
||||
header[#header+1] = ""
|
||||
header[#header+1] = ""
|
||||
|
||||
-- Output
|
||||
stat, code, msg = client:writeall(table.concat(header, "\r\n"))
|
||||
|
||||
if sourceout and stat then
|
||||
if util.instanceof(sourceout, IOResource) then
|
||||
stat, code, msg = sourceout.fd:copyz(client, sourceout.len)
|
||||
else
|
||||
stat, msg = ltn12.pump.all(sourceout, sinkout)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Write errors
|
||||
if not stat then
|
||||
if msg then
|
||||
nixio.syslog("err", "Error sending data to " .. env.host ..
|
||||
": " .. msg .. "\n")
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
if buffer then
|
||||
sourcehdr(buffer)
|
||||
end
|
||||
until close
|
||||
|
||||
client:shutdown()
|
||||
client:close()
|
||||
end
|
2
libs/lucid/Makefile
Normal file
2
libs/lucid/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
include ../../build/config.mk
|
||||
include ../../build/module.mk
|
61
libs/lucid/hostfiles/etc/config/lucid
Normal file
61
libs/lucid/hostfiles/etc/config/lucid
Normal file
|
@ -0,0 +1,61 @@
|
|||
config lucid main
|
||||
option pollinterval 15000
|
||||
option daemon 1
|
||||
option debug 1
|
||||
list supports tcpserver
|
||||
list supports server
|
||||
|
||||
config DirectoryPublisher webroot
|
||||
option name 'Webserver Share'
|
||||
option physical host/www
|
||||
option virtual ''
|
||||
option domain ''
|
||||
list read ':lo'
|
||||
list read ':br-lan'
|
||||
list read 'root'
|
||||
|
||||
config LuciWebPublisher luciweb
|
||||
option name 'LuCI Webapplication'
|
||||
option physical ''
|
||||
list virtual /luci
|
||||
option domain ''
|
||||
list exec ':lo'
|
||||
list exec ':br-lan'
|
||||
list exec 'root'
|
||||
|
||||
config RPCPublisher mainrpc
|
||||
option namespace 'luci.lucid.rpc'
|
||||
list export system
|
||||
list export ruci
|
||||
list exec ':lo'
|
||||
list exec 'root'
|
||||
|
||||
config tcpserver httpd
|
||||
option entrypoint "luci.lucid.http"
|
||||
list supports DirectoryPublisher
|
||||
list supports LuciWebPublisher
|
||||
|
||||
config tcpserver rpcd
|
||||
option entrypoint "luci.lucid.rpc"
|
||||
list supports RPCPublisher
|
||||
|
||||
config daemon http
|
||||
option slave httpd
|
||||
list address 8080
|
||||
list publisher webroot
|
||||
list publisher luciweb
|
||||
option enabled 1
|
||||
|
||||
config daemon https
|
||||
option slave httpd
|
||||
list address 4443
|
||||
list publisher webroot
|
||||
list publisher luciweb
|
||||
option enabled 1
|
||||
option encryption enable
|
||||
|
||||
config daemon rpc
|
||||
option slave rpcd
|
||||
list address 12900
|
||||
list publisher mainrpc
|
||||
option enabled 1
|
266
libs/lucid/luasrc/lucid.lua
Normal file
266
libs/lucid/luasrc/lucid.lua
Normal file
|
@ -0,0 +1,266 @@
|
|||
--[[
|
||||
LuCI - Lua Development Framework
|
||||
|
||||
Copyright 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]
|
||||
|
||||
local nixio = require "nixio"
|
||||
local table = require "table"
|
||||
local uci = require "luci.model.uci"
|
||||
local os = require "os"
|
||||
local io = require "io"
|
||||
|
||||
local pairs, require, pcall, assert, type = pairs, require, pcall, assert, type
|
||||
local ipairs, tonumber, collectgarbage = ipairs, tonumber, collectgarbage
|
||||
|
||||
|
||||
module "luci.lucid"
|
||||
|
||||
local slaves = {}
|
||||
local pollt = {}
|
||||
local tickt = {}
|
||||
local tpids = {}
|
||||
local tcount = 0
|
||||
local ifaddrs = nixio.getifaddrs()
|
||||
|
||||
cursor = uci.cursor()
|
||||
state = uci.cursor_state()
|
||||
UCINAME = "lucid"
|
||||
|
||||
local cursor = cursor
|
||||
local state = state
|
||||
local UCINAME = UCINAME
|
||||
local SSTATE = "/tmp/.lucid_store"
|
||||
|
||||
|
||||
|
||||
function start()
|
||||
prepare()
|
||||
|
||||
local detach = cursor:get(UCINAME, "main", "daemonize")
|
||||
if detach == "1" then
|
||||
local stat, code, msg = daemonize()
|
||||
if not stat then
|
||||
nixio.syslog("crit", "Unable to detach process: " .. msg .. "\n")
|
||||
ox.exit(2)
|
||||
end
|
||||
end
|
||||
|
||||
run()
|
||||
end
|
||||
|
||||
function prepare()
|
||||
local debug = tonumber((cursor:get(UCINAME, "main", "debug")))
|
||||
|
||||
nixio.openlog("lucid", "pid", "perror")
|
||||
if debug ~= 1 then
|
||||
nixio.setlogmask("warning")
|
||||
end
|
||||
|
||||
cursor:foreach(UCINAME, "daemon", function(config)
|
||||
if config.enabled ~= "1" then
|
||||
return
|
||||
end
|
||||
|
||||
local key = config[".name"]
|
||||
if not config.slave then
|
||||
nixio.syslog("crit", "Daemon "..key.." is missing a slave\n")
|
||||
os.exit(1)
|
||||
else
|
||||
nixio.syslog("info", "Initializing daemon " .. key)
|
||||
end
|
||||
|
||||
state:revert(UCINAME, key)
|
||||
|
||||
local daemon, code, err = prepare_daemon(config)
|
||||
if daemon then
|
||||
state:set(UCINAME, key, "status", "started")
|
||||
nixio.syslog("info", "Prepared daemon " .. key)
|
||||
else
|
||||
state:set(UCINAME, key, "status", "error")
|
||||
state:set(UCINAME, key, "error", err)
|
||||
nixio.syslog("err", "Failed to initialize daemon "..key..": "..
|
||||
err .. "\n")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function run()
|
||||
local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval")))
|
||||
|
||||
while true do
|
||||
local stat, code = nixio.poll(pollt, pollint)
|
||||
|
||||
if stat and stat > 0 then
|
||||
for _, polle in ipairs(pollt) do
|
||||
if polle.revents ~= 0 and polle.handler then
|
||||
polle.handler(polle)
|
||||
end
|
||||
end
|
||||
elseif stat == 0 then
|
||||
ifaddrs = nixio.getifaddrs()
|
||||
collectgarbage("collect")
|
||||
end
|
||||
|
||||
for _, cb in ipairs(tickt) do
|
||||
cb()
|
||||
end
|
||||
|
||||
local pid, stat, code = nixio.wait(-1, "nohang")
|
||||
while pid and pid > 0 do
|
||||
tcount = tcount - 1
|
||||
if tpids[pid] and tpids[pid] ~= true then
|
||||
tpids[pid](pid, stat, code)
|
||||
end
|
||||
pid, stat, code = nixio.wait(-1, "nohang")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function register_pollfd(polle)
|
||||
pollt[#pollt+1] = polle
|
||||
return true
|
||||
end
|
||||
|
||||
function unregister_pollfd(polle)
|
||||
for k, v in ipairs(pollt) do
|
||||
if v == polle then
|
||||
table.remove(pollt, k)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function close_pollfds()
|
||||
for k, v in ipairs(pollt) do
|
||||
if v.fd and v.fd.close then
|
||||
v.fd:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function register_tick(cb)
|
||||
tickt[#tickt+1] = cb
|
||||
return true
|
||||
end
|
||||
|
||||
function unregister_tick(cb)
|
||||
for k, v in ipairs(tickt) do
|
||||
if v == cb then
|
||||
table.remove(tickt, k)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function create_process(threadcb, waitcb)
|
||||
local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
|
||||
if threadlimit and #tpids >= tcount then
|
||||
nixio.syslog("warning", "Unable to create thread: process limit reached")
|
||||
return nil
|
||||
end
|
||||
local pid, code, err = nixio.fork()
|
||||
if pid and pid ~= 0 then
|
||||
tpids[pid] = waitcb
|
||||
tcount = tcount + 1
|
||||
elseif pid == 0 then
|
||||
local code = threadcb()
|
||||
os.exit(code)
|
||||
else
|
||||
nixio.syslog("err", "Unable to fork(): " .. err)
|
||||
end
|
||||
return pid, code, err
|
||||
end
|
||||
|
||||
function prepare_daemon(config)
|
||||
nixio.syslog("info", "Preparing daemon " .. config[".name"])
|
||||
local modname = cursor:get(UCINAME, config.slave)
|
||||
if not modname then
|
||||
return nil, -1, "invalid slave"
|
||||
end
|
||||
|
||||
local stat, module = pcall(require, _NAME .. "." .. modname)
|
||||
if not stat or not module.prepare_daemon then
|
||||
return nil, -2, "slave type not supported"
|
||||
end
|
||||
|
||||
config.slave = prepare_slave(config.slave)
|
||||
|
||||
return module.prepare_daemon(config, _M)
|
||||
end
|
||||
|
||||
function prepare_slave(name)
|
||||
local slave = slaves[name]
|
||||
if not slave then
|
||||
local config = cursor:get_all(UCINAME, name)
|
||||
|
||||
local stat, module = pcall(require, config and config.entrypoint)
|
||||
if stat then
|
||||
slave = {module = module, config = config}
|
||||
end
|
||||
end
|
||||
|
||||
if slave then
|
||||
return slave
|
||||
else
|
||||
return nil, module
|
||||
end
|
||||
end
|
||||
|
||||
function get_interfaces()
|
||||
return ifaddrs
|
||||
end
|
||||
|
||||
function revoke_privileges(user, group)
|
||||
if nixio.getuid() == 0 then
|
||||
return nixio.setgid(group) and nixio.setuid(user)
|
||||
end
|
||||
end
|
||||
|
||||
function securestate()
|
||||
local stat = nixio.fs.stat(SSTATE) or {}
|
||||
local uid = nixio.getuid()
|
||||
if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
|
||||
nixio.fs.remover(SSTATE)
|
||||
if not nixio.fs.mkdir(SSTATE, 700) then
|
||||
local errno = nixio.errno()
|
||||
nixio.syslog("err", "Integrity check on secure state failed!")
|
||||
return nil, errno, nixio.perror(errno)
|
||||
end
|
||||
end
|
||||
|
||||
return uci.cursor(nil, SSTATE)
|
||||
end
|
||||
|
||||
function daemonize()
|
||||
if nixio.getppid() == 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local pid, code, msg = nixio.fork()
|
||||
if not pid then
|
||||
return nil, code, msg
|
||||
elseif pid > 0 then
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
nixio.setsid()
|
||||
nixio.chdir("/")
|
||||
|
||||
local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
|
||||
nixio.dup(devnull, nixio.stdin)
|
||||
nixio.dup(devnull, nixio.stdout)
|
||||
nixio.dup(devnull, nixio.stderr)
|
||||
|
||||
return true
|
||||
end
|
192
libs/lucid/luasrc/lucid/tcpserver.lua
Normal file
192
libs/lucid/luasrc/lucid/tcpserver.lua
Normal file
|
@ -0,0 +1,192 @@
|
|||
--[[
|
||||
LuCI - Lua Development Framework
|
||||
|
||||
Copyright 2009 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]
|
||||
|
||||
local os = require "os"
|
||||
local nixio = require "nixio"
|
||||
local lucid = require "luci.lucid"
|
||||
|
||||
local ipairs, type, require, setmetatable = ipairs, type, require, setmetatable
|
||||
local pairs, print, tostring, unpack = pairs, print, tostring, unpack
|
||||
|
||||
module "luci.lucid.tcpserver"
|
||||
|
||||
local cursor = lucid.cursor
|
||||
local UCINAME = lucid.UCINAME
|
||||
|
||||
local tcpsockets = {}
|
||||
|
||||
|
||||
function prepare_daemon(config, server)
|
||||
nixio.syslog("info", "Preparing TCP-Daemon " .. config[".name"])
|
||||
if type(config.address) ~= "table" then
|
||||
config.address = {config.address}
|
||||
end
|
||||
|
||||
local sockets, socket, code, err = {}
|
||||
local sopts = {reuseaddr = 1}
|
||||
for _, addr in ipairs(config.address) do
|
||||
local host, port = addr:match("(.-):?([^:]*)")
|
||||
if not host then
|
||||
nixio.syslog("err", "Invalid address: " .. addr)
|
||||
return nil, -5, "invalid address format"
|
||||
elseif #host == 0 then
|
||||
host = nil
|
||||
end
|
||||
socket, code, err = prepare_socket(config.family, host, port, sopts)
|
||||
if socket then
|
||||
sockets[#sockets+1] = socket
|
||||
end
|
||||
end
|
||||
|
||||
nixio.syslog("info", "Sockets bound for " .. config[".name"])
|
||||
|
||||
if #sockets < 1 then
|
||||
return nil, -6, "no sockets bound"
|
||||
end
|
||||
|
||||
nixio.syslog("info", "Preparing publishers for " .. config[".name"])
|
||||
|
||||
local publisher = {}
|
||||
for k, pname in ipairs(config.publisher) do
|
||||
local pdata = cursor:get_all(UCINAME, pname)
|
||||
if pdata then
|
||||
publisher[#publisher+1] = pdata
|
||||
else
|
||||
nixio.syslog("err", "Publisher " .. pname .. " not found")
|
||||
end
|
||||
end
|
||||
|
||||
nixio.syslog("info", "Preparing TLS for " .. config[".name"])
|
||||
|
||||
local tls = prepare_tls(config.tls)
|
||||
if not tls and config.encryption == "enable" then
|
||||
for _, s in ipairs(sockets) do
|
||||
s:close()
|
||||
end
|
||||
return nil, -4, "Encryption requested, but no TLS context given"
|
||||
end
|
||||
|
||||
nixio.syslog("info", "Invoking daemon factory for " .. config[".name"])
|
||||
local handler, err = config.slave.module.factory(publisher, config)
|
||||
if not handler then
|
||||
for _, s in ipairs(sockets) do
|
||||
s:close()
|
||||
end
|
||||
return nil, -3, err
|
||||
else
|
||||
local pollin = nixio.poll_flags("in")
|
||||
for _, s in ipairs(sockets) do
|
||||
server.register_pollfd({
|
||||
fd = s,
|
||||
events = pollin,
|
||||
revents = 0,
|
||||
handler = accept,
|
||||
accept = handler,
|
||||
config = config,
|
||||
publisher = publisher,
|
||||
tls = tls
|
||||
})
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function accept(polle)
|
||||
local socket, host, port = polle.fd:accept()
|
||||
if not socket then
|
||||
return nixio.syslog("warn", "accept() failed: " .. port)
|
||||
end
|
||||
|
||||
socket:setblocking(true)
|
||||
|
||||
local function thread()
|
||||
lucid.close_pollfds()
|
||||
local inst = setmetatable({
|
||||
host = host, port = port, interfaces = lucid.get_interfaces()
|
||||
}, {__index = polle})
|
||||
if polle.config.encryption then
|
||||
socket = polle.tls:create(socket)
|
||||
if not socket:accept() then
|
||||
socket:close()
|
||||
return nixio.syslog("warning", "TLS handshake failed: " .. host)
|
||||
end
|
||||
end
|
||||
|
||||
return polle.accept(socket, inst)
|
||||
end
|
||||
|
||||
local stat = {lucid.create_process(thread)}
|
||||
socket:close()
|
||||
return unpack(stat)
|
||||
end
|
||||
|
||||
function prepare_socket(family, host, port, opts, backlog)
|
||||
nixio.syslog("info", "Preparing socket for port " .. port)
|
||||
backlog = backlog or 1024
|
||||
family = family or "inetany"
|
||||
opts = opts or {}
|
||||
|
||||
local inetany = family == "inetany"
|
||||
family = inetany and "inet6" or family
|
||||
|
||||
local socket, code, err = nixio.socket(family, "stream")
|
||||
if not socket and inetany then
|
||||
family = "inet"
|
||||
socket, code, err = nixio.socket(family, "stream")
|
||||
end
|
||||
|
||||
if not socket then
|
||||
return nil, code, err
|
||||
end
|
||||
|
||||
for k, v in pairs(opts) do
|
||||
socket:setsockopt("socket", k, v)
|
||||
end
|
||||
|
||||
local stat, code, err = socket:bind(host, port)
|
||||
if not stat then
|
||||
return nil, code, err
|
||||
end
|
||||
|
||||
stat, code, err = socket:listen(backlog)
|
||||
if not stat then
|
||||
return nil, code, err
|
||||
end
|
||||
|
||||
socket:setblocking(false)
|
||||
|
||||
return socket, family
|
||||
end
|
||||
|
||||
function prepare_tls(tlskey)
|
||||
local tls = nixio.tls()
|
||||
if tlskey and cursor:get(UCINAME, tlskey) then
|
||||
local cert = cursor:get(UCINAME, tlskey, "cert")
|
||||
if cert then
|
||||
tls:set_cert(cert)
|
||||
end
|
||||
local key = cursor:get(UCINAME, tlskey, "key")
|
||||
if key then
|
||||
tls:set_key(key)
|
||||
end
|
||||
local ciphers = cursor:get(UCINAME, tlskey, "ciphers")
|
||||
if ciphers then
|
||||
if type(ciphers) == "table" then
|
||||
ciphers = table.concat(ciphers, ":")
|
||||
end
|
||||
tls:set_ciphers(ciphers)
|
||||
end
|
||||
end
|
||||
return tls
|
||||
end
|
61
libs/lucid/root/etc/config/lucid
Normal file
61
libs/lucid/root/etc/config/lucid
Normal file
|
@ -0,0 +1,61 @@
|
|||
config lucid main
|
||||
option pollinterval 15000
|
||||
option threadlimit 25
|
||||
option daemon 1
|
||||
option debug 1
|
||||
list supports tcpserver
|
||||
list supports server
|
||||
|
||||
config DirectoryPublisher webroot
|
||||
option name 'Webserver Share'
|
||||
option physical /www
|
||||
option virtual /
|
||||
option domain ''
|
||||
list read ':lo'
|
||||
list read ':br-lan'
|
||||
list read 'root'
|
||||
|
||||
config LuciWebPublisher luciweb
|
||||
option name 'LuCI Webapplication'
|
||||
option physical ''
|
||||
list virtual /luci
|
||||
option domain ''
|
||||
list exec ':lo'
|
||||
list exec ':br-lan'
|
||||
list exec 'root'
|
||||
|
||||
config RPCPublisher mainrpc
|
||||
option namespace 'luci.lucid.rpc'
|
||||
list export system
|
||||
list exec ':lo'
|
||||
list exec 'root'
|
||||
|
||||
config tcpserver httpd
|
||||
option entrypoint "luci.lucid.http"
|
||||
list supports DirectoryPublisher
|
||||
list supports LuciWebPublisher
|
||||
|
||||
config tcpserver rpcd
|
||||
option entrypoint "luci.lucid.rpc"
|
||||
list supports RPCPublisher
|
||||
|
||||
config daemon http
|
||||
option slave httpd
|
||||
list address 8080
|
||||
list publisher webroot
|
||||
list publisher luciweb
|
||||
option enabled 1
|
||||
|
||||
config daemon https
|
||||
option slave httpd
|
||||
list address 4443
|
||||
list publisher webroot
|
||||
list publisher luciweb
|
||||
option enabled 1
|
||||
option encryption enable
|
||||
|
||||
config daemon rpc
|
||||
option slave rpcd
|
||||
list address 12900
|
||||
list publisher mainrpc
|
||||
option enabled 1
|
1
libs/nixio/.gitignore
vendored
1
libs/nixio/.gitignore
vendored
|
@ -8,3 +8,4 @@ lkc_defs.h
|
|||
mconf
|
||||
zconf.tab.h
|
||||
zconf.tab.c
|
||||
src/nixio.dll
|
||||
|
|
|
@ -79,7 +79,7 @@ function copy(src, dest)
|
|||
end
|
||||
elseif stat.type == "lnk" then
|
||||
res, code, msg = nixio.symlink(nixio.readlink(src), dest)
|
||||
else
|
||||
elseif stat.type == "reg" then
|
||||
res, code, msg = datacopy(src, dest)
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ $Id$
|
|||
|
||||
local table = require "table"
|
||||
local nixio = require "nixio"
|
||||
local getmetatable, assert, pairs = getmetatable, assert, pairs
|
||||
local getmetatable, assert, pairs, type = getmetatable, assert, pairs, type
|
||||
|
||||
module "nixio.util"
|
||||
|
||||
|
@ -106,7 +106,7 @@ function meta.linesource(self, limit)
|
|||
|
||||
if flush then
|
||||
line = buffer:sub(bpos + 1)
|
||||
buffer = ""
|
||||
buffer = type(flush) == "string" and flush or ""
|
||||
bpos = 0
|
||||
return line
|
||||
end
|
||||
|
@ -189,8 +189,11 @@ end
|
|||
|
||||
function meta.copyz(self, fd, size)
|
||||
local sent, lsent, code, msg = 0
|
||||
local splicable
|
||||
|
||||
if self:is_file() then
|
||||
if nixio.sendfile and fd:is_socket() and self:stat("type") == "reg" then
|
||||
local ftype = self:stat("type")
|
||||
if nixio.sendfile and fd:is_socket() and ftype == "reg" then
|
||||
repeat
|
||||
lsent, code, msg = nixio.sendfile(fd, self, size or ZIOBLKSIZE)
|
||||
if lsent then
|
||||
|
@ -202,7 +205,27 @@ function meta.copyz(self, fd, size)
|
|||
code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then
|
||||
return lsent and sent, code, msg, sent
|
||||
end
|
||||
end
|
||||
elseif nixio.splice and not fd:is_tls_socket() and ftype == "fifo" then
|
||||
splicable = true
|
||||
end
|
||||
end
|
||||
|
||||
if nixio.splice and fd:is_file() and not splicable then
|
||||
splicable = not self:is_tls_socket() and fd:stat("type") == "fifo"
|
||||
end
|
||||
|
||||
if splicable then
|
||||
repeat
|
||||
lsent, code, msg = nixio.splice(self, fd, size or ZIOBLKSIZE)
|
||||
if lsent then
|
||||
sent = sent + lsent
|
||||
size = size and (size - lsent)
|
||||
end
|
||||
until (not lsent or lsent == 0 or (size and size == 0))
|
||||
if lsent or (not lsent and sent == 0 and
|
||||
code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then
|
||||
return lsent and sent, code, msg, sent
|
||||
end
|
||||
end
|
||||
|
||||
return self:copy(fd, size)
|
||||
|
@ -212,6 +235,24 @@ function tls_socket.close(self)
|
|||
return self.socket:close()
|
||||
end
|
||||
|
||||
function tls_socket.getsockname(self)
|
||||
return self.socket:getsockname()
|
||||
end
|
||||
|
||||
function tls_socket.getpeername(self)
|
||||
return self.socket:getpeername()
|
||||
end
|
||||
|
||||
function tls_socket.getsockopt(self, ...)
|
||||
return self.socket:getsockopt(...)
|
||||
end
|
||||
tls_socket.getopt = tls_socket.getsockopt
|
||||
|
||||
function tls_socket.setsockopt(self, ...)
|
||||
return self.socket:setsockopt(...)
|
||||
end
|
||||
tls_socket.setopt = tls_socket.setsockopt
|
||||
|
||||
for k, v in pairs(meta) do
|
||||
file[k] = v
|
||||
socket[k] = v
|
||||
|
|
Loading…
Reference in a new issue