diff options
author | Steven Barth <steven@midlink.org> | 2009-05-23 17:21:36 +0000 |
---|---|---|
committer | Steven Barth <steven@midlink.org> | 2009-05-23 17:21:36 +0000 |
commit | 8c4f847ea5b95aaf0e716beaf736b4e2b67655ae (patch) | |
tree | 5b931064a2df45c3903b66b425f49b621e9c31df /libs/lucid-http/luasrc/lucid/http/handler | |
parent | 0ad58e38b42dca2f46bc492b7f889b5031a9c6a1 (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.lua | 53 | ||||
-rw-r--r-- | libs/lucid-http/luasrc/lucid/http/handler/file.lua | 250 | ||||
-rw-r--r-- | libs/lucid-http/luasrc/lucid/http/handler/luci.lua | 96 |
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 + |