3 HTTP server implementation for LuCI - file handler
4 (c) 2008 Steven Barth <steven@midlink.org>
5 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
17 local ipairs, type, tonumber = ipairs, type, tonumber
18 local io = require "io"
19 local os = require "os"
20 local fs = require "luci.fs"
21 local util = require "luci.util"
22 local ltn12 = require "luci.ltn12"
23 local mod = require "luci.ttpd.module"
24 local srv = require "luci.ttpd.server"
25 local string = require "string"
27 local prot = require "luci.http.protocol"
28 local date = require "luci.http.protocol.date"
29 local mime = require "luci.http.protocol.mime"
30 local cond = require "luci.http.protocol.conditionals"
32 module "luci.ttpd.handler.file"
34 Simple = util.class(mod.Handler)
35 Response = mod.Response
37 function Simple.__init__(self, docroot, dirlist)
38 mod.Handler.__init__(self)
39 self.docroot = docroot
40 self.dirlist = dirlist and true or false
43 function Simple.parse_range(self, request, size)
44 if not request.headers.Range then
48 local from, to = request.headers.Range:match("bytes=([0-9]*)-([0-9]*)")
49 if not (from or to) then
53 from, to = tonumber(from), tonumber(to)
54 if not (from or to) then
57 from, to = size - to, size - 1
72 local range = "bytes " .. from .. "-" .. to .. "/" .. size
73 return from, (1 + to - from), range
76 function Simple.getfile(self, uri)
77 local file = self.docroot .. uri:gsub("%.%./+", "")
78 local stat = fs.stat(file)
83 function Simple.handle_get(self, request, sourcein, sinkerr)
84 local file, stat = self:getfile( prot.urldecode( request.env.PATH_INFO, true ) )
87 if stat.type == "regular" then
89 -- Generate Entity Tag
90 local etag = cond.mk_etag( stat )
95 ok, code, hdrs = cond.if_modified_since( request, stat )
97 ok, code, hdrs = cond.if_match( request, stat )
99 ok, code, hdrs = cond.if_unmodified_since( request, stat )
101 ok, code, hdrs = cond.if_none_match( request, stat )
103 local f, err = io.open(file)
107 local o, s, r = self:parse_range(request, stat.size)
110 return self:failure(416, "Invalid Range")
114 ["Last-Modified"] = date.to_http( stat.mtime ),
115 ["Content-Type"] = mime.to_mime( file ),
117 ["Accept-Ranges"] = "bytes",
125 headers["Content-Range"] = r
128 headers["Content-Length"] = s
131 return Response(code, headers),
132 srv.IOResource(f, o, s)
134 return self:failure( 403, err:gsub("^.+: ", "") )
137 return Response( code, hdrs or { } )
140 return Response( code, hdrs or { } )
143 return Response( code, hdrs or { } )
146 return Response( code, hdrs or { } )
149 elseif stat.type == "directory" then
151 local ruri = request.request_uri:gsub("/$","")
152 local duri = prot.urldecode( ruri, true )
153 local root = self.docroot:gsub("/$","")
155 -- check for index files
156 local index_candidates = {
157 "index.html", "index.htm", "default.html", "default.htm",
158 "index.txt", "default.txt"
161 -- try to find an index file and redirect to it
162 for i, candidate in ipairs( index_candidates ) do
163 local istat = fs.stat(
164 root .. "/" .. duri .. "/" .. candidate
167 if istat ~= nil and istat.type == "regular" then
168 return Response( 302, {
169 ["Location"] = ruri .. "/" .. candidate
175 local html = string.format(
176 '<?xml version="1.0" encoding="ISO-8859-15"?>\n' ..
177 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' ..
178 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' ..
179 '<html xmlns="http://www.w3.org/1999/xhtml" ' ..
180 'xml:lang="en" lang="en">\n' ..
182 '<title>Index of %s/</title>\n' ..
183 '<style type="text/css"><!--\n' ..
184 'body { background-color:#fbb034; color:#ffffff } ' ..
185 'li { border-bottom:1px dotted #CCCCCC; padding:3px } ' ..
186 'small { font-size:60%%; color:#ffffff } ' ..
188 '\n--></style></head><body><h1>Index of %s/</h1><hr /><ul>',
192 local entries = fs.dir( file )
194 if type(entries) == "table" then
195 for i, e in util.spairs(
196 entries, function(a,b)
197 if entries[a] == '..' then
199 elseif entries[b] == '..' then
202 return ( entries[a] < entries[b] )
206 if e ~= '.' and ( e == '..' or e:sub(1,1) ~= '.' ) then
207 local estat = fs.stat( file .. "/" .. e )
209 if estat.type == "directory" then
210 html = html .. string.format(
211 '<li><p><a href="%s/%s/">%s/</a> ' ..
212 '<small>(directory)</small><br />' ..
213 '<small>Changed: %s</small></li>',
214 ruri, prot.urlencode( e ), e,
215 date.to_http( estat.mtime )
218 html = html .. string.format(
219 '<li><p><a href="%s/%s">%s</a> ' ..
220 '<small>(%s)</small><br />' ..
221 '<small>Size: %i Bytes | ' ..
222 'Changed: %s</small></li>',
223 ruri, prot.urlencode( e ), e,
225 estat.size, date.to_http( estat.mtime )
231 html = html .. '</ul><hr /></body></html>'
235 ["Date"] = date.to_http( os.time() );
236 ["Content-Type"] = "text/html; charset=ISO-8859-15";
238 ), ltn12.source.string(html)
240 return self:failure(403, "Permission denied")
243 return self:failure(403, "Unable to transmit " .. stat.type .. " " .. file)
246 return self:failure(404, "No such file: " .. file)
250 function Simple.handle_head(self, ...)
251 return (self:handle_get(...))