253 lines
6.7 KiB
Lua
253 lines
6.7 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 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
|