diff options
Diffstat (limited to 'libs/lucittpd/luasrc/ttpd/handler/file.lua')
-rw-r--r-- | libs/lucittpd/luasrc/ttpd/handler/file.lua | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/libs/lucittpd/luasrc/ttpd/handler/file.lua b/libs/lucittpd/luasrc/ttpd/handler/file.lua new file mode 100644 index 0000000000..e1f707c628 --- /dev/null +++ b/libs/lucittpd/luasrc/ttpd/handler/file.lua @@ -0,0 +1,252 @@ +--[[ + +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 io = require "io" +local os = require "os" +local fs = require "luci.fs" +local util = require "luci.util" +local ltn12 = require "luci.ltn12" +local mod = require "luci.ttpd.module" +local srv = require "luci.ttpd.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.ttpd.handler.file" + +Simple = util.class(mod.Handler) +Response = mod.Response + +function Simple.__init__(self, docroot, dirlist) + mod.Handler.__init__(self) + self.docroot = docroot + self.dirlist = dirlist and true or false +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) + local file = self.docroot .. uri:gsub("%.%./+", "") + local stat = fs.stat(file) + + return file, stat +end + +function Simple.handle_get(self, request, sourcein, sinkerr) + local file, stat = self:getfile( prot.urldecode( request.env.PATH_INFO, true ) ) + + if stat then + if stat.type == "regular" 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 = io.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 + o = 0 + s = stat.size + else + code = 206 + headers["Content-Range"] = r + end + + headers["Content-Length"] = s + + -- Send Response + return Response(code, headers), + srv.IOResource(f, o, s) + else + return self:failure( 403, err:gsub("^.+: ", "") ) + end + else + return Response( code, hdrs or { } ) + end + else + return Response( code, hdrs or { } ) + end + else + return Response( code, hdrs or { } ) + end + else + return Response( code, hdrs or { } ) + end + + elseif stat.type == "directory" then + + local ruri = request.request_uri:gsub("/$","") + local duri = prot.urldecode( ruri, true ) + local root = self.docroot:gsub("/$","") + + -- 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 == "regular" then + return Response( 302, { + ["Location"] = ruri .. "/" .. candidate + } ) + end + end + + + local html = string.format( + '<?xml version="1.0" encoding="ISO-8859-15"?>\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 { background-color:#fbb034; color:#ffffff } ' .. + 'li { border-bottom:1px dotted #CCCCCC; padding:3px } ' .. + 'small { font-size:60%%; color:#ffffff } ' .. + 'p { margin:0 }' .. + '\n--></style></head><body><h1>Index of %s/</h1><hr /><ul>', + duri, duri + ) + + local entries = fs.dir( file ) + + if type(entries) == "table" then + for i, e in util.spairs( + entries, function(a,b) + if entries[a] == '..' then + return true + elseif entries[b] == '..' then + return false + else + return ( entries[a] < entries[b] ) + end + end + ) do + if e ~= '.' and ( e == '..' or e:sub(1,1) ~= '.' ) then + local estat = fs.stat( file .. "/" .. e ) + + if estat.type == "directory" 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 + end + + html = html .. '</ul><hr /></body></html>' + + return Response( + 200, { + ["Date"] = date.to_http( os.time() ); + ["Content-Type"] = "text/html; charset=ISO-8859-15"; + } + ), 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 + return self:failure(404, "No such file: " .. file) + end +end + +function Simple.handle_head(self, ...) + return (self:handle_get(...)) +end |