92cf7ed0ab7a57083e087207313280586d160fef
[project/luci.git] / libs / httpd / luasrc / httpd / handler / file.lua
1 --[[
2
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>
6
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
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17 module("luci.httpd.handler.file", package.seeall)
18
19 require("luci.httpd.module")
20 require("luci.http.protocol.date")
21 require("luci.http.protocol.mime")
22 require("luci.http.protocol.conditionals")
23 require("luci.fs")
24 local ltn12 = require("luci.ltn12")
25
26 Simple = luci.util.class(luci.httpd.module.Handler)
27 Response = luci.httpd.module.Response
28
29 function Simple.__init__(self, docroot, dirlist)
30 luci.httpd.module.Handler.__init__(self)
31 self.docroot = docroot
32 self.dirlist = dirlist and true or false
33 self.proto = luci.http.protocol
34 self.mime = luci.http.protocol.mime
35 self.date = luci.http.protocol.date
36 self.cond = luci.http.protocol.conditionals
37 end
38
39 function Simple.getfile(self, uri)
40 local file = self.docroot .. uri:gsub("%.%./+", "")
41 local stat = luci.fs.stat(file)
42
43 return file, stat
44 end
45
46 function Simple.handle_get(self, request, sourcein, sinkerr)
47 local file, stat = self:getfile( self.proto.urldecode( request.env.PATH_INFO, true ) )
48
49 if stat then
50 if stat.type == "regular" then
51
52 -- Generate Entity Tag
53 local etag = self.cond.mk_etag( stat )
54
55 -- Check conditionals
56 local ok, code, hdrs
57
58 ok, code, hdrs = self.cond.if_modified_since( request, stat )
59 if ok then
60 ok, code, hdrs = self.cond.if_match( request, stat )
61 if ok then
62 ok, code, hdrs = self.cond.if_unmodified_since( request, stat )
63 if ok then
64 ok, code, hdrs = self.cond.if_none_match( request, stat )
65 if ok then
66 local f, err = io.open(file)
67
68 if f then
69 -- Send Response
70 return Response(
71 200, {
72 ["Date"] = self.date.to_http( os.time() );
73 ["Last-Modified"] = self.date.to_http( stat.mtime );
74 ["Content-Type"] = self.mime.to_mime( file );
75 ["Content-Length"] = stat.size;
76 ["ETag"] = etag;
77 }
78 ), ltn12.source.file(f)
79 else
80 return self:failure( 403, err:gsub("^.+: ", "") )
81 end
82 else
83 return Response( code, hdrs or { } )
84 end
85 else
86 return Response( code, hdrs or { } )
87 end
88 else
89 return Response( code, hdrs or { } )
90 end
91 else
92 return Response( code, hdrs or { } )
93 end
94
95 elseif stat.type == "directory" then
96
97 local ruri = request.request_uri:gsub("/$","")
98 local duri = self.proto.urldecode( ruri, true )
99 local root = self.docroot:gsub("/$","")
100
101 -- check for index files
102 local index_candidates = {
103 "index.html", "index.htm", "default.html", "default.htm",
104 "index.txt", "default.txt"
105 }
106
107 -- try to find an index file and redirect to it
108 for i, candidate in ipairs( index_candidates ) do
109 local istat = luci.fs.stat(
110 root .. "/" .. duri .. "/" .. candidate
111 )
112
113 if istat ~= nil and istat.type == "regular" then
114 return Response( 301, {
115 ["Location"] = ruri .. "/" .. candidate
116 } ), ltn12.source.empty()
117 end
118 end
119
120
121 local html = string.format(
122 '<?xml version="1.0" encoding="ISO-8859-15"?>\n' ..
123 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' ..
124 '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' ..
125 '<html xmlns="http://www.w3.org/1999/xhtml" ' ..
126 'xml:lang="en" lang="en">\n' ..
127 '<head>\n' ..
128 '<title>Index of %s/</title>\n' ..
129 '<style type="text/css"><!--\n' ..
130 'body { background-color:#FFFFFF; color:#000000 } ' ..
131 'li { border-bottom:1px dotted #CCCCCC; padding:3px } ' ..
132 'small { font-size:60%%; color:#999999 } ' ..
133 'p { margin:0 }' ..
134 '\n--></style></head><body><h1>Index of %s/</h1><hr /><ul>',
135 duri, duri
136 )
137
138 local entries = luci.fs.dir( file )
139
140 if type(entries) == "table" then
141 for i, e in luci.util.spairs(
142 entries, function(a,b)
143 if entries[a] == '..' then
144 return true
145 elseif entries[b] == '..' then
146 return false
147 else
148 return ( entries[a] < entries[b] )
149 end
150 end
151 ) do
152 if e ~= '.' and ( e == '..' or e:sub(1,1) ~= '.' ) then
153 local estat = luci.fs.stat( file .. "/" .. e )
154
155 if estat.type == "directory" then
156 html = html .. string.format(
157 '<li><p><a href="%s/%s/">%s/</a> ' ..
158 '<small>(directory)</small><br />' ..
159 '<small>Changed: %s</small></li>',
160 ruri, self.proto.urlencode( e ), e,
161 self.date.to_http( estat.mtime )
162 )
163 else
164 html = html .. string.format(
165 '<li><p><a href="%s/%s">%s</a> ' ..
166 '<small>(%s)</small><br />' ..
167 '<small>Size: %i Bytes | ' ..
168 'Changed: %s</small></li>',
169 ruri, self.proto.urlencode( e ), e,
170 self.mime.to_mime( e ),
171 estat.size, self.date.to_http( estat.mtime )
172 )
173 end
174 end
175 end
176
177 html = html .. '</ul><hr /></body></html>'
178
179 return Response(
180 200, {
181 ["Date"] = self.date.to_http( os.time() );
182 ["Content-Type"] = "text/html; charset=ISO-8859-15";
183 }
184 ), ltn12.source.string(html)
185 else
186 return self:failure(403, "Permission denied")
187 end
188 else
189 return self:failure(403, "Unable to transmit " .. stat.type .. " " .. file)
190 end
191 else
192 return self:failure(404, "No such file: " .. file)
193 end
194 end
195
196 function Simple.handle_head(self, ...)
197 local response, sourceout = self:handle_get(...)
198 return response
199 end