From 8c4f847ea5b95aaf0e716beaf736b4e2b67655ae Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Sat, 23 May 2009 17:21:36 +0000 Subject: [PATCH] GSoC Commit #1: LuCId + HTTP-Server --- Makefile | 5 +- build/module.mk | 3 - build/setup.lua | 11 + libs/lucid-http/Makefile | 2 + libs/lucid-http/luasrc/lucid/http.lua | 33 ++ .../luasrc/lucid/http/DirectoryPublisher.lua | 47 ++ .../luasrc/lucid/http/LuciWebPublisher.lua | 62 +++ .../luasrc/lucid/http/Redirector.lua | 31 ++ .../luasrc/lucid/http/handler/catchall.lua | 53 ++ .../luasrc/lucid/http/handler/file.lua | 250 +++++++++ .../luasrc/lucid/http/handler/luci.lua | 96 ++++ libs/lucid-http/luasrc/lucid/http/server.lua | 522 ++++++++++++++++++ libs/lucid/Makefile | 2 + libs/lucid/hostfiles/etc/config/lucid | 61 ++ libs/lucid/luasrc/lucid.lua | 266 +++++++++ libs/lucid/luasrc/lucid/tcpserver.lua | 192 +++++++ libs/lucid/root/etc/config/lucid | 61 ++ libs/nixio/.gitignore | 1 + libs/nixio/lua/nixio/fs.lua | 2 +- libs/nixio/lua/nixio/util.lua | 49 +- 20 files changed, 1739 insertions(+), 10 deletions(-) create mode 100644 build/setup.lua create mode 100644 libs/lucid-http/Makefile create mode 100644 libs/lucid-http/luasrc/lucid/http.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/Redirector.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/handler/catchall.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/handler/file.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/handler/luci.lua create mode 100644 libs/lucid-http/luasrc/lucid/http/server.lua create mode 100644 libs/lucid/Makefile create mode 100644 libs/lucid/hostfiles/etc/config/lucid create mode 100644 libs/lucid/luasrc/lucid.lua create mode 100644 libs/lucid/luasrc/lucid/tcpserver.lua create mode 100644 libs/lucid/root/etc/config/lucid diff --git a/Makefile b/Makefile index 1b9e25916..0f0bf42bf 100644 --- a/Makefile +++ b/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 diff --git a/build/module.mk b/build/module.mk index ecc7a92f2..569de101d 100644 --- a/build/module.mk +++ b/build/module.mk @@ -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 diff --git a/build/setup.lua b/build/setup.lua new file mode 100644 index 000000000..dbe9bdcc9 --- /dev/null +++ b/build/setup.lua @@ -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 + diff --git a/libs/lucid-http/Makefile b/libs/lucid-http/Makefile new file mode 100644 index 000000000..2bdfad16e --- /dev/null +++ b/libs/lucid-http/Makefile @@ -0,0 +1,2 @@ +include ../../build/module.mk +include ../../build/config.mk \ No newline at end of file diff --git a/libs/lucid-http/luasrc/lucid/http.lua b/libs/lucid-http/luasrc/lucid/http.lua new file mode 100644 index 000000000..32ba5791d --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http.lua @@ -0,0 +1,33 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua b/libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua new file mode 100644 index 000000000..f471781a9 --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua @@ -0,0 +1,47 @@ +--[[ +LuCId HTTP-Slave +(c) 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua b/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua new file mode 100644 index 000000000..0d0648967 --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua @@ -0,0 +1,62 @@ +--[[ +LuCId HTTP-Slave +(c) 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid-http/luasrc/lucid/http/Redirector.lua b/libs/lucid-http/luasrc/lucid/http/Redirector.lua new file mode 100644 index 000000000..c0af90b00 --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/Redirector.lua @@ -0,0 +1,31 @@ +--[[ +LuCId HTTP-Slave +(c) 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua b/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua new file mode 100644 index 000000000..0523751bc --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua @@ -0,0 +1,53 @@ +--[[ +LuCId HTTP-Slave +(c) 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid-http/luasrc/lucid/http/handler/file.lua b/libs/lucid-http/luasrc/lucid/http/handler/file.lua new file mode 100644 index 000000000..d08e47025 --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/handler/file.lua @@ -0,0 +1,250 @@ +--[[ + +HTTP server implementation for LuCI - file handler +(c) 2008 Steven Barth +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +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( + '\n' .. + '\n'.. + '\n' .. + '\n' .. + 'Index of %s/\n' .. + '

Index of %s/


    '.. + '
  • ../ ' .. + '(parent directory)
    ' .. + '

  • ', + 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( + '
  • %s/ ' .. + '(directory)
    ' .. + 'Changed: %s

  • ', + ruri, prot.urlencode( e ), e, + date.to_http( estat.mtime ) + ) + else + html = html .. string.format( + '
  • %s ' .. + '(%s)
    ' .. + 'Size: %i Bytes | ' .. + 'Changed: %s

  • ', + ruri, prot.urlencode( e ), e, + mime.to_mime( e ), + estat.size, date.to_http( estat.mtime ) + ) + end + end + + html = html .. '

LuCId-HTTPd' .. + '
' + + 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 diff --git a/libs/lucid-http/luasrc/lucid/http/handler/luci.lua b/libs/lucid-http/luasrc/lucid/http/handler/luci.lua new file mode 100644 index 000000000..c54e39366 --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/handler/luci.lua @@ -0,0 +1,96 @@ +--[[ +LuCId HTTP-Slave +(c) 2009 Steven Barth + +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 + diff --git a/libs/lucid-http/luasrc/lucid/http/server.lua b/libs/lucid-http/luasrc/lucid/http/server.lua new file mode 100644 index 000000000..f5de4e9a1 --- /dev/null +++ b/libs/lucid-http/luasrc/lucid/http/server.lua @@ -0,0 +1,522 @@ +--[[ +LuCId HTTP-Slave +(c) 2009 Steven Barth + +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 diff --git a/libs/lucid/Makefile b/libs/lucid/Makefile new file mode 100644 index 000000000..f7fac7740 --- /dev/null +++ b/libs/lucid/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk diff --git a/libs/lucid/hostfiles/etc/config/lucid b/libs/lucid/hostfiles/etc/config/lucid new file mode 100644 index 000000000..5a732ac53 --- /dev/null +++ b/libs/lucid/hostfiles/etc/config/lucid @@ -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 \ No newline at end of file diff --git a/libs/lucid/luasrc/lucid.lua b/libs/lucid/luasrc/lucid.lua new file mode 100644 index 000000000..62741e79f --- /dev/null +++ b/libs/lucid/luasrc/lucid.lua @@ -0,0 +1,266 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid/luasrc/lucid/tcpserver.lua b/libs/lucid/luasrc/lucid/tcpserver.lua new file mode 100644 index 000000000..22f094529 --- /dev/null +++ b/libs/lucid/luasrc/lucid/tcpserver.lua @@ -0,0 +1,192 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth + +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 \ No newline at end of file diff --git a/libs/lucid/root/etc/config/lucid b/libs/lucid/root/etc/config/lucid new file mode 100644 index 000000000..dda68d901 --- /dev/null +++ b/libs/lucid/root/etc/config/lucid @@ -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 \ No newline at end of file diff --git a/libs/nixio/.gitignore b/libs/nixio/.gitignore index a210ca014..a21f2a2d7 100644 --- a/libs/nixio/.gitignore +++ b/libs/nixio/.gitignore @@ -8,3 +8,4 @@ lkc_defs.h mconf zconf.tab.h zconf.tab.c +src/nixio.dll diff --git a/libs/nixio/lua/nixio/fs.lua b/libs/nixio/lua/nixio/fs.lua index f745ffe78..35d20b29c 100644 --- a/libs/nixio/lua/nixio/fs.lua +++ b/libs/nixio/lua/nixio/fs.lua @@ -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 diff --git a/libs/nixio/lua/nixio/util.lua b/libs/nixio/lua/nixio/util.lua index 2c9fc93a3..6f929519b 100644 --- a/libs/nixio/lua/nixio/util.lua +++ b/libs/nixio/lua/nixio/util.lua @@ -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 -- 2.25.1