* libs/http: removed protocol.filter, added mimetypes to protocol.mime
[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 ) )
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 )
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                         for i, e in luci.util.spairs(
141                                 entries, function(a,b)
142                                         if entries[a] == '..' then
143                                                 return true
144                                         elseif entries[b] == '..' then
145                                                 return false
146                                         else
147                                                 return ( entries[a] < entries[b] )
148                                         end
149                                 end
150                         ) do
151                                 if e ~= '.' and ( e == '..' or e:sub(1,1) ~= '.' ) then
152                                         local estat = luci.fs.stat( file .. "/" .. e )
153
154                                         if estat.type == "directory" then
155                                                 html = html .. string.format(
156                                                         '<li><p><a href="%s/%s/">%s/</a> '               ..
157                                                         '<small>(directory)</small><br />'               ..
158                                                         '<small>Changed: %s</small></li>',
159                                                                 ruri, self.proto.urlencode( e ), e,
160                                                                 self.date.to_http( estat.mtime )
161                                                 )
162                                         else
163                                                 html = html .. string.format(
164                                                         '<li><p><a href="%s/%s">%s</a> '                 ..
165                                                         '<small>(%s)</small><br />'                      ..
166                                                         '<small>Size: %i Bytes | Changed: %s</small></li>',
167                                                                 ruri, self.proto.urlencode( e ), e,
168                                                                 self.mime.to_mime( e ),
169                                                                 estat.size, self.date.to_http( estat.mtime )
170                                                 )
171                                         end
172                                 end
173                         end
174
175                         html = html .. '</ul><hr /></body></html>'
176
177                         return Response(
178                                 200, {
179                                         ["Date"]         = self.date.to_http( os.time() );
180                                         ["Content-Type"] = "text/html; charset=ISO-8859-15";
181                                 }
182                         ), ltn12.source.string(html)
183                 else
184                         return self:failure(403, "Unable to transmit " .. stat.type .. " " .. file)
185                 end
186         else
187                 return self:failure(404, "No such file: " .. file)
188         end
189 end
190
191 function Simple.handle_head(self, ...)
192         local response, sourceout = self:handle_get(...)
193         return response
194 end