flm01/mote/v2/openwrt/package/luci/libs/lucid-http/luasrc/lucid/http/handler/file.lua

251 lines
6.5 KiB
Lua

--[[
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