summaryrefslogtreecommitdiffhomepage
path: root/libs/lucid-http/luasrc/lucid/http/handler
diff options
context:
space:
mode:
authorSteven Barth <steven@midlink.org>2009-05-23 17:21:36 +0000
committerSteven Barth <steven@midlink.org>2009-05-23 17:21:36 +0000
commit8c4f847ea5b95aaf0e716beaf736b4e2b67655ae (patch)
tree5b931064a2df45c3903b66b425f49b621e9c31df /libs/lucid-http/luasrc/lucid/http/handler
parent0ad58e38b42dca2f46bc492b7f889b5031a9c6a1 (diff)
GSoC Commit #1: LuCId + HTTP-Server
Diffstat (limited to 'libs/lucid-http/luasrc/lucid/http/handler')
-rw-r--r--libs/lucid-http/luasrc/lucid/http/handler/catchall.lua53
-rw-r--r--libs/lucid-http/luasrc/lucid/http/handler/file.lua250
-rw-r--r--libs/lucid-http/luasrc/lucid/http/handler/luci.lua96
3 files changed, 399 insertions, 0 deletions
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 <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 \ 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 <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
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 <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
+