* libs/http: added support for directory listings
[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 require("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.mime    = luci.http.protocol.mime
34         self.date    = luci.http.protocol.date
35         self.cond    = luci.http.protocol.conditionals
36 end
37
38 function Simple.getfile(self, uri)
39         local file = self.docroot .. uri:gsub("%.%./+", "")
40         local stat = luci.fs.stat(file)
41
42         return file, stat
43 end
44
45 function Simple.handle_get(self, request, sourcein, sinkerr)
46         local file, stat = self:getfile(request.env.PATH_INFO)
47
48         if stat then
49                 if stat.type == "regular" then
50
51                         -- Generate Entity Tag
52                         local etag = self.cond.mk_etag( stat )
53
54                         -- Check conditionals
55                         local ok, code, hdrs
56
57                         ok, code, hdrs = self.cond.if_modified_since( request, stat )
58                         if ok then
59                                 ok, code, hdrs = self.cond.if_match( request, stat )
60                                 if ok then
61                                         ok, code, hdrs = self.cond.if_unmodified_since( request, stat )
62                                         if ok then
63                                                 ok, code, hdrs = self.cond.if_none_match( request, stat )
64                                                 if ok then
65                                                         -- Send Response
66                                                         return Response(
67                                                                 200, {
68                                                                         ["Date"]           = self.date.to_http( os.time() );
69                                                                         ["Last-Modified"]  = self.date.to_http( stat.mtime );
70                                                                         ["Content-Type"]   = self.mime.to_mime( file );
71                                                                         ["Content-Length"] = stat.size;
72                                                                         ["ETag"]           = etag;
73                                                                 }
74                                                         ), ltn12.source.file(io.open(file))
75                                                 else
76                                                         return Response( code, hdrs or { } )
77                                                 end
78                                         else
79                                                 return Response( code, hdrs or { } )
80                                         end
81                                 else
82                                         return Response( code, hdrs or { } )
83                                 end
84                         else
85                                 return Response( code, hdrs or { } )
86                         end
87
88                 elseif stat.type == "directory" then
89
90                         local ruri = request.request_uri:gsub("/$","")
91                         local root = self.docroot:gsub("/$","")
92
93                         -- check for index files
94                         local index_candidates = {
95                                 "index.html", "index.htm", "default.html", "default.htm",
96                                 "index.txt", "default.txt"
97                         }
98
99                         -- try to find an index file and redirect to it
100                         for i, candidate in ipairs( index_candidates ) do
101                                 local istat = luci.fs.stat(
102                                         root .. "/" .. ruri .. "/" .. candidate
103                                 )
104
105                                 if istat ~= nil and istat.type == "regular" then
106                                         return Response( 301, {
107                                                 ["Location"] = ruri .. "/" .. candidate
108                                         } ), ltn12.source.empty()
109                                 end
110                         end
111
112
113                         local html = string.format(
114                                 '<?xml version="1.0" encoding="UTF-8"?>\n' ..
115                                 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '  ..
116                                         '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' ..
117                                 '<html xmlns="http://www.w3.org/1999/xhtml" '                ..
118                                         'xml:lang="en" lang="en">\n'                             ..
119                                 '<head>\n'                                                   ..
120                                 '<title>Index of %s</title>\n'                               ..
121                                 '</head><body><h1>Index of %s</h1><hr /><ul>',
122                                         file, file
123                         )
124
125                         for i, e in luci.util.vspairs( luci.fs.dir( file ) ) do
126
127                                 if e ~= '.' then
128                                         local estat = luci.fs.stat( file .. "/" .. e )
129
130                                         if estat.type == "directory" then
131                                                 html = html .. string.format(
132                                                         '<li><p><a href="%s/%s/">%s/</a> '                         ..
133                                                         '<small>(directory)</small><br />'                              ..
134                                                         '<small>Changed: %s</small></li>',
135                                                                 ruri, e, e,
136                                                                 self.date.to_http( estat.mtime )
137                                                 )
138                                         else
139                                                 html = html .. string.format(
140                                                         '<li><p><a href="%s/%s">%s</a> '                         ..
141                                                         '<small>(%s)</small><br />'                              ..
142                                                         '<small>Size: %i Bytes | Changed: %s</small></li>',
143                                                                 ruri, e, e, self.mime.to_mime( e ),
144                                                                 estat.size, self.date.to_http( estat.mtime )
145                                                 )
146                                         end
147                                 end
148                         end
149
150                         html = html .. '</ul><hr /></body></html>'
151
152                         return Response(
153                                 200, {
154                                         ["Date"]         = self.date.to_http( os.time() );
155                                         ["Content-Type"] = "text/html";
156                                 }
157                         ), ltn12.source.string(html)
158                 else
159                         return self:failure(403, "Unable to transmit " .. stat.type .. " " .. file)
160                 end
161         else
162                 return self:failure(404, "No such file: " .. file)
163         end
164 end
165
166 function Simple.handle_head(self, ...)
167         local response, sourceout = self:handle_get(...)
168         return response
169 end