]> git.sesse.net Git - vlc/blob - share/lua/intf/http.lua
Update lua intf scripts to new API (untested).
[vlc] / share / lua / intf / http.lua
1 --[==========================================================================[
2  http.lua: HTTP interface module for VLC
3 --[==========================================================================[
4  Copyright (C) 2007 the VideoLAN team
5  $Id$
6
7  Authors: Antoine Cellerier <dionoea at videolan dot org>
8
9  This program is free software; you can redistribute it and/or modify
10  it under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or
12  (at your option) any later version.
13
14  This program is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  GNU General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with this program; if not, write to the Free Software
21  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 --]==========================================================================]
23
24 --[==========================================================================[
25 Configuration options:
26  * host: A host to listen on.
27  * dir: Directory to use as the http interface's root.
28  * no_error_detail: If set, do not print the Lua error message when generating
29                     a page fails.
30  * no_index: If set, don't build directory indexes
31 --]==========================================================================]
32
33
34 require "common"
35
36 vlc.msg.info("Lua HTTP interface")
37
38 open_tag = "<?vlc"
39 close_tag = "?>"
40
41 function escape(s)
42     return (string.gsub(s,"([%^%$%%%.%[%]%*%+%-%?])","%%%1"))
43 end
44
45 function process_raw(filename)
46     local input = io.open(filename):read("*a")
47     -- find the longest [===[ or ]=====] type sequence and make sure that
48     -- we use one that's longer.
49     local str="X"
50     for str2 in string.gmatch(input,"[%[%]]=*[%[%]]") do
51         if #str < #str2 then str = str2 end
52     end
53     str=string.rep("=",#str-1)
54
55     --[[ FIXME:
56     <?xml version="1.0" encoding="charset" standalone="yes" ?> is still a problem. The closing '?>' needs to be printed using '?<?vlc print ">" ?>' to prevent a parse error.
57     --]]
58     local code0 = string.gsub(input,escape(close_tag)," print(["..str.."[")
59     local code1 = string.gsub(code0,escape(open_tag),"]"..str.."]) ")
60     local code = "print(["..str.."["..code1.."]"..str.."])"
61     --[[ Uncomment to debug
62     if string.match(filename,"vlm_cmd.xml$") then
63     io.write(code)
64     io.write("\n")
65     end
66     --]]
67     return assert(loadstring(code,filename))
68 end
69 function process(filename)
70     local mtime = 0    -- vlc.net.stat(filename).modification_time
71     local func = false -- process_raw(filename)
72     return function(...)
73         local new_mtime = vlc.net.stat(filename).modification_time
74         if new_mtime ~= mtime then
75             -- Re-read the file if it changed
76             if mtime == 0 then
77                 vlc.msg.dbg("Loading `"..filename.."'")
78             else
79                 vlc.msg.dbg("Reloading `"..filename.."'")
80             end
81             func = process_raw(filename)
82             mtime = new_mtime
83         end
84         return func(...)
85     end
86 end
87
88
89 function callback_error(path,url,msg)
90     local url = url or "&lt;page unknown&gt;"
91     return  [[<html xmlns="http://www.w3.org/1999/xhtml">
92 <head>
93 <title>Error loading ]]..url..[[</title>
94 </head>
95 <body>
96 <h1>Error loading ]]..url..[[</h1><pre>]]..(config.no_error_detail and "Remove configuration option `no_error_detail' on the server to get more information." or tostring(msg))..[[</pre>
97 <p>
98 <a href="http://www.videolan.org/">VideoLAN</a><br/>
99 <a href="http://www.lua.org/manual/5.1/">Lua 5.1 Reference Manual</a>
100 </p>
101 </body>
102 </html>]]
103 end
104
105 function dirlisting(url,listing,acl_)
106     local list = {}
107     for _,f in ipairs(listing) do
108         if not string.match(f,"^%.") then
109             table.insert(list,"<li><a href='"..f.."'>"..f.."</a></li>")
110         end
111     end
112     list = table.concat(list)
113     local function callback()
114         return [[<html xmlns="http://www.w3.org/1999/xhtml">
115 <head>
116 <title>Directory listing ]]..url..[[</title>
117 </head>
118 <body>
119 <h1>Directory listing ]]..url..[[</h1><ul>]]..list..[[</ul>
120 </body>
121 </html>]]
122     end
123     return h:file(url,"text/html",nil,nil,acl_,callback,nil)
124 end
125
126 function file(h,path,url,acl_,mime)
127     local generate_page = process(path)
128     local callback = function(data,request)
129         -- FIXME: I'm sure that we could define a real sandbox
130         -- redefine print
131         local page = {}
132         local function pageprint(...)
133             for i=1,select("#",...) do
134                 if i== 1 then
135                     table.insert(page,tostring(select(i,...)))
136                 else
137                     table.insert(page," "..tostring(select(i,...)))
138                 end
139             end
140         end
141         _G._GET = parse_url_request(request)
142         local oldprint = print
143         print = pageprint
144         local ok, msg = pcall(generate_page)
145         -- reset
146         print = oldprint
147         if not ok then
148             return callback_error(path,url,msg)
149         end
150         return table.concat(page)
151     end
152     return h:file(url or path,mime,nil,nil,acl_,callback,nil)
153 end
154
155 function rawfile(h,path,url,acl_)
156     local filename = path
157     local mtime = 0    -- vlc.net.stat(filename).modification_time
158     local page = false -- io.open(filename):read("*a")
159     local callback = function(data,request)
160         local new_mtime = vlc.net.stat(filename).modification_time
161         if mtime ~= new_mtime then
162             -- Re-read the file if it changed
163             if mtime == 0 then
164                 vlc.msg.dbg("Loading `"..filename.."'")
165             else
166                 vlc.msg.dbg("Reloading `"..filename.."'")
167             end
168             page = io.open(filename):read("*a")
169             mtime = new_mtime
170         end
171         return page
172     end
173     return h:file(url or path,nil,nil,nil,acl_,callback,nil)
174 end
175
176 function parse_url_request(request)
177     if not request then return {} end
178     t = {}
179     for k,v in string.gmatch(request,"([^=&]+)=?([^=&]*)") do
180         local k_ = vlc.strings.decode_uri(k)
181         local v_ = vlc.strings.decode_uri(v)
182         if t[k_] ~= nil then
183             local t2
184             if type(t[k_]) ~= "table" then
185                 t2 = {}
186                 table.insert(t2,t[k_])
187                 t[k_] = t2
188             else
189                 t2 = t[k_]
190             end
191             table.insert(t2,v_)
192         else
193             t[k_] = v_
194         end
195     end
196     return t
197 end
198
199 local function find_datadir(name)
200     local list = vlc.misc.datadir_list(name)
201     for _, l in ipairs(list) do
202         local s = vlc.net.stat(l)
203         if s then
204             return l
205         end
206     end
207     error("Unable to find the `"..name.."' directory.")
208 end
209 http_dir = config.dir or find_datadir("http")
210
211 do
212     local oldpath = package.path
213     package.path = http_dir.."/?.lua"
214     local ok, err = pcall(require,"custom")
215     if not ok then
216         vlc.msg.warn("Couldn't load "..http_dir.."/custom.lua")
217     else
218         vlc.msg.dbg("Loaded "..http_dir.."/custom.lua")
219     end
220     package.path = oldpath
221 end
222 local files = {}
223 local mimes = {
224     txt = "text/plain",
225     html = "text/html",
226     xml = "text/xml",
227     js = "text/javascript",
228     css = "text/css",
229     png = "image/png",
230     ico = "image/x-icon",
231 }
232 local function load_dir(dir,root,parent_acl)
233     local root = root or "/"
234     local has_index = false
235     local my_acl = parent_acl
236     do
237         local af = dir.."/.hosts"
238         local s = vlc.net.stat(af)
239         if s and s.type == "file" then
240             -- We found an acl
241             my_acl = vlc.acl(false)
242             my_acl:load_file(af)
243         end
244     end
245     local d = vlc.net.opendir(dir)
246     for _,f in ipairs(d) do
247         if not string.match(f,"^%.") then
248             local s = vlc.net.stat(dir.."/"..f)
249             if s.type == "file" then
250                 local url
251                 if f == "index.html" then
252                     url = root
253                     has_index = true
254                 else
255                     url = root..f
256                 end
257                 local ext = string.match(f,"%.([^%.]-)$")
258                 local mime = mimes[ext]
259                 -- print(url,mime)
260                 if mime and string.match(mime,"^text/") then
261                     table.insert(files,file(h,dir.."/"..f,url,my_acl and my_acl.private,mime))
262                 else
263                     table.insert(files,rawfile(h,dir.."/"..f,url,my_acl and my_acl.private))
264                 end
265             elseif s.type == "dir" then
266                 load_dir(dir.."/"..f,root..f.."/",my_acl)
267             end
268         end
269     end
270     if not has_index and not config.no_index then
271         -- print("Adding index for", root)
272         table.insert(files,dirlisting(root,d,my_acl and my_acl.private))
273     end
274 end
275
276 local u = vlc.net.url_parse( config.host or "localhost:8080" )
277 h = vlc.httpd(u.host,u.port)
278 load_dir( http_dir )
279
280 while not die do die = vlc.misc.lock_and_wait() end -- everything happens in callbacks
281
282 -- FIXME: We shouldn't need to do this ourselves.
283 for i=1,#files do
284     getmetatable(files[i]).__gc(files[i])
285     files[i] = nil
286 end