summaryrefslogtreecommitdiffhomepage
path: root/libs/lucid-http
diff options
context:
space:
mode:
Diffstat (limited to 'libs/lucid-http')
-rw-r--r--libs/lucid-http/Makefile2
-rw-r--r--libs/lucid-http/docs/OVERVIEW17
-rw-r--r--libs/lucid-http/luasrc/lucid/http.lua36
-rw-r--r--libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua50
-rw-r--r--libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua73
-rw-r--r--libs/lucid-http/luasrc/lucid/http/Redirector.lua33
-rw-r--r--libs/lucid-http/luasrc/lucid/http/handler/catchall.lua87
-rw-r--r--libs/lucid-http/luasrc/lucid/http/handler/file.lua272
-rw-r--r--libs/lucid-http/luasrc/lucid/http/handler/luci.lua113
-rw-r--r--libs/lucid-http/luasrc/lucid/http/server.lua600
10 files changed, 0 insertions, 1283 deletions
diff --git a/libs/lucid-http/Makefile b/libs/lucid-http/Makefile
deleted file mode 100644
index 2bdfad16e5..0000000000
--- a/libs/lucid-http/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-include ../../build/module.mk
-include ../../build/config.mk \ No newline at end of file
diff --git a/libs/lucid-http/docs/OVERVIEW b/libs/lucid-http/docs/OVERVIEW
deleted file mode 100644
index 74b499ca9f..0000000000
--- a/libs/lucid-http/docs/OVERVIEW
+++ /dev/null
@@ -1,17 +0,0 @@
- LuCId HTTP/1.1 Server Slave
-
-*** Abstract ***
-The LuCId HTTP-Server Slave is an HTTP/1.1 implementation for the LuCId
-superserver loosely based on the LuCI HTTP stack. It supports keep-alive,
-pipelining, basic authentication, kernel-mode file transfer (sendfile()
-through nixio), address and hostname based virtual hosts, custom 404 pages,
-E-Tags, conditional headers, directory indexing and partial file transfers.
-
-
-*** Workflow ***
-After receiving an incoming connection from LuCId, the slave parses the request
-and prepares the environment for the acion handler. After that the virtual host
-will be dispatched and the request will be passed on to the respective handler.
-The handler will enforce access restrictions if configured and then returns a
-status code a set of response headers, as well as a content resource that will
-be sent to the user. \ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http.lua b/libs/lucid-http/luasrc/lucid/http.lua
deleted file mode 100644
index 931967c563..0000000000
--- a/libs/lucid-http/luasrc/lucid/http.lua
+++ /dev/null
@@ -1,36 +0,0 @@
---[[
-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"
-
---- Prepare the HTTP-daemon and its associated publishers.
--- @param publisher Table of publishers
--- @return factory callback or nil, error message
-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
deleted file mode 100644
index 09cade49e1..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua
+++ /dev/null
@@ -1,50 +0,0 @@
---[[
-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"
-
-
---- Prepare a directory publisher and assign it to a given Virtual Host.
--- @param server HTTP daemon object
--- @param config publisher configuration
-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 or "", handler)
- end
-end
diff --git a/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua b/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua
deleted file mode 100644
index 5dc70df841..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua
+++ /dev/null
@@ -1,73 +0,0 @@
---[[
-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"
-
-
---- Prepare a LuCI web publisher and assign it to a given Virtual Host.
--- @param server HTTP daemon object
--- @param config publisher configuration
-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
-
- local mypath
- if type(config.virtual) == "table" then
- for _, v in ipairs(config.virtual) do
- mypath = mypath or v
- vhost:set_handler(v, handler)
- end
- else
- mypath = config.virtual
- vhost:set_handler(config.virtual or "", handler)
- end
-
- if config.home then
- vhost.default = mypath
- end
-end
diff --git a/libs/lucid-http/luasrc/lucid/http/Redirector.lua b/libs/lucid-http/luasrc/lucid/http/Redirector.lua
deleted file mode 100644
index 66a86a7ff7..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/Redirector.lua
+++ /dev/null
@@ -1,33 +0,0 @@
---[[
-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"
-
---- Prepare a redirector publisher and assign it to a given Virtual Host.
--- @param server HTTP daemon object
--- @param config publisher configuration
-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 or "", handler)
-end
diff --git a/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua b/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua
deleted file mode 100644
index 13272d91e4..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua
+++ /dev/null
@@ -1,87 +0,0 @@
---[[
-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"
-local util = require "luci.util"
-local ip = require "luci.ip"
-local ipairs = ipairs
-
---- Catchall Handler
--- @cstyle instance
-module "luci.lucid.http.handler.catchall"
-
---- Create a Redirect handler.
--- @param name Name
--- @param target Redirect Target
--- @class function
--- @return Redirect handler object
-Redirect = util.class(srv.Handler)
-
-function Redirect.__init__(self, name, target)
- srv.Handler.__init__(self, name)
- self.target = target
-end
-
---- Handle a GET request.
--- @param request Request object
--- @return status code, header table, response source
-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 request.env.REMOTE_ADDR and not request.env.REMOTE_ADDR:find(":") then
- local compare = ip.IPv4(request.env.REMOTE_ADDR)
- for _, iface in ipairs(request.server.interfaces) do
- if iface.family == "inet" and iface.addr and iface.netmask then
- if ip.IPv4(iface.addr, iface.netmask):contains(compare) then
- server = iface.addr
- break
- end
- end
- end
- end
-
- 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
-
---- Handle a POST request.
--- @class function
--- @param request Request object
--- @return status code, header table, response source
-Redirect.handle_POST = Redirect.handle_GET
-
---- Handle a HEAD request.
--- @class function
--- @param request Request object
--- @return status code, header table, response source
-function Redirect.handle_HEAD(self, request)
- local stat, head = self:handle_GET(request)
- return stat, head
-end
diff --git a/libs/lucid-http/luasrc/lucid/http/handler/file.lua b/libs/lucid-http/luasrc/lucid/http/handler/file.lua
deleted file mode 100644
index 4f29c8bd28..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/handler/file.lua
+++ /dev/null
@@ -1,272 +0,0 @@
---[[
-
-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"
-
---- File system handler
--- @cstyle instance
-module "luci.lucid.http.handler.file"
-
---- Create a simple file system handler.
--- @class function
--- @param name Name
--- @param docroot Physical Document Root
--- @param options Options
--- @return Simple file system handler object
-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
-
---- Parse a range request.
--- @param request Request object
--- @param size File size
--- @return offset, length, range header or boolean status
-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
-
---- Translate path and return file information.
--- @param uri Request URI
--- @return physical file path, file information
-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
-
---- Handle a GET request.
--- @param request Request object
--- @return status code, header table, response source
-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 = {
- ["Cache-Control"] = "max-age=29030400",
- ["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
-
---- Handle a HEAD request.
--- @param request Request object
--- @return status code, header table, response source
-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
deleted file mode 100644
index b6d92deec2..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/handler/luci.lua
+++ /dev/null
@@ -1,113 +0,0 @@
---[[
-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
-
---- LuCI web handler
--- @cstyle instance
-module "luci.lucid.http.handler.luci"
-
---- Create a LuCI web handler.
--- @class function
--- @param name Name
--- @param prefix Dispatching prefix
--- @return LuCI web handler object
-Luci = util.class(srv.Handler)
-
-function Luci.__init__(self, name, prefix)
- srv.Handler.__init__(self, name)
- self.prefix = prefix
- dsp.indexcache = "/tmp/luci-indexcache"
-end
-
---- Handle a HEAD request.
--- @param request Request object
--- @return status code, header table, response source
-function Luci.handle_HEAD(self, ...)
- local stat, head = self:handle_GET(...)
- return stat, head
-end
-
---- Handle a POST request.
--- @param request Request object
--- @return status code, header table, response source
-function Luci.handle_POST(self, ...)
- return self:handle_GET(...)
-end
-
---- Handle a GET request.
--- @param request Request object
--- @return status code, header table, response source
-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
deleted file mode 100644
index fd5f7cdd8b..0000000000
--- a/libs/lucid-http/luasrc/lucid/http/server.lua
+++ /dev/null
@@ -1,600 +0,0 @@
---[[
-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 set_memory_limit = set_memory_limit
-
-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"
-
---- HTTP Daemon
--- @cstyle instance
-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",
-}
-
---- Create a new IO resource response.
--- @class function
--- @param fd File descriptor
--- @param len Length of data
--- @return IO resource
-IOResource = util.class()
-
-function IOResource.__init__(self, fd, len)
- self.fd, self.len = fd, len
-end
-
-
---- Create a server handler.
--- @class function
--- @param name Name
--- @return Handler
-Handler = util.class()
-
-function Handler.__init__(self, name)
- self.name = name or tostring(self)
-end
-
---- Create a failure reply.
--- @param code HTTP status code
--- @param msg Status message
--- @return status code, header table, response source
-function Handler.failure(self, code, msg)
- return code, { ["Content-Type"] = "text/plain" }, ltn12.source.string(msg)
-end
-
---- Add an access restriction.
--- @param restriction Restriction specification
-function Handler.restrict(self, restriction)
- if not self.restrictions then
- self.restrictions = {restriction}
- else
- self.restrictions[#self.restrictions+1] = restriction
- end
-end
-
---- Enforce access restrictions.
--- @param request Request object
--- @return nil or HTTP statuscode, table of headers, response source
-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
- request.env.HTTP_AUTH_USER, request.env.HTTP_AUTH_PASS = user, pass
- return
- end
- end
-
- return 401, {
- ["WWW-Authenticate"] = ('Basic realm=%q'):format(self.name),
- ["Content-Type"] = 'text/plain'
- }, ltn12.source.string("Unauthorized")
-end
-
---- Process a request.
--- @param request Request object
--- @param sourcein Request data source
--- @return HTTP statuscode, table of headers, response source
-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
-
-
---- Create a Virtual Host.
--- @class function
--- @return Virtual Host
-VHost = util.class()
-
-function VHost.__init__(self)
- self.handlers = {}
-end
-
---- Process a request and invoke the appropriate handler.
--- @param request Request object
--- @param ... Additional parameters passed to the handler
--- @return HTTP statuscode, table of headers, response source
-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
-
- if self.default and uri == "/" then
- return 302, {Location = self.default}
- end
-
- 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
-
---- Get a list of registered handlers.
--- @return Table of handlers
-function VHost.get_handlers(self)
- return self.handlers
-end
-
---- Register handler with a given URI prefix.
--- @oaram match URI prefix
--- @param handler Handler object
-function VHost.set_handler(self, match, handler)
- self.handlers[match] = handler
-end
-
--- Remap IPv6-IPv4-compatibility addresses back to IPv4 addresses.
-local function remapipv6(adr)
- local map = "::ffff:"
- if adr:sub(1, #map) == map then
- return adr:sub(#map+1)
- else
- return adr
- end
-end
-
--- Create a source that decodes chunked-encoded data from a socket.
-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
-
--- Create a sink that chunk-encodes data and writes it on a given socket.
-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, tostring(chunk)))
- end
- end
-end
-
-
---- Create a server object.
--- @class function
--- @return Server object
-Server = util.class()
-
-function Server.__init__(self)
- self.vhosts = {}
-end
-
---- Get a list of registered virtual hosts.
--- @return Table of virtual hosts
-function Server.get_vhosts(self)
- return self.vhosts
-end
-
---- Register a virtual host with a given name.
--- @param name Hostname
--- @param vhost Virtual host object
-function Server.set_vhost(self, name, vhost)
- self.vhosts[name] = vhost
-end
-
---- Send a fatal error message to given client and close the connection.
--- @param client Client socket
--- @param code HTTP status code
--- @param msg status message
-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"
-}
-
---- Parse the request headers and prepare the environment.
--- @param source line-based input source
--- @return Request object
-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
-
---- Handle a new client connection.
--- @param client client socket
--- @param env superserver environment
-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
-
- env.config.memlimit = tonumber(env.config.memlimit)
- if env.config.memlimit and set_memory_limit then
- set_memory_limit(env.config.memlimit)
- end
-
- client:setsockopt("socket", "rcvtimeo", 5)
- client:setsockopt("socket", "sndtimeo", 5)
-
- 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
-
- close = true
- 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"] and not close then
- if message.env.SERVER_PROTOCOL == "HTTP/1.1" then
- headers["Transfer-Encoding"] = "chunked"
- sinkout = chunksink(client)
- else
- close = true
- end
- end
- elseif message.env.REQUEST_METHOD ~= "HEAD" then
- headers["Content-Length"] = 0
- end
-
- if close then
- headers["Connection"] = "close"
- else
- headers["Connection"] = "Keep-Alive"
- headers["Keep-Alive"] = "timeout=5, max=50"
- 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
- local closefd
- if util.instanceof(sourceout, IOResource) then
- if not headers["Transfer-Encoding"] then
- stat, code, msg = sourceout.fd:copyz(client, sourceout.len)
- closefd = sourceout.fd
- sourceout = nil
- else
- closefd = sourceout.fd
- sourceout = sourceout.fd:blocksource(nil, sourceout.len)
- end
- end
-
- if sourceout then
- stat, msg = ltn12.pump.all(sourceout, sinkout)
- end
-
- if closefd then
- closefd:close()
- 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