]> git.sesse.net Git - vlc/blob - share/lua/intf/http.lua
use vlc.input.item() where appropriate
[vlc] / share / lua / intf / http.lua
1 --[==========================================================================[
2  http.lua: HTTP interface module for VLC
3 --[==========================================================================[
4  Copyright (C) 2007-2009 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 -- TODO: use internal VLC mime lookup function for mimes not included here
42 local mimes = {
43     txt = "text/plain",
44     html = "text/html",
45     xml = "text/xml",
46     js = "text/javascript",
47     css = "text/css",
48     png = "image/png",
49     jpg = "image/jpeg",
50     jpeg = "image/jpeg",
51     ico = "image/x-icon",
52 }
53
54 function escape(s)
55     return (string.gsub(s,"([%^%$%%%.%[%]%*%+%-%?])","%%%1"))
56 end
57
58 function process_raw(filename)
59     local input = io.open(filename):read("*a")
60     -- find the longest [===[ or ]=====] type sequence and make sure that
61     -- we use one that's longer.
62     local str="X"
63     for str2 in string.gmatch(input,"[%[%]]=*[%[%]]") do
64         if #str < #str2 then str = str2 end
65     end
66     str=string.rep("=",#str-1)
67
68     --[[ FIXME:
69     <?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.
70     --]]
71     local code0 = string.gsub(input,escape(close_tag)," print(["..str.."[")
72     local code1 = string.gsub(code0,escape(open_tag),"]"..str.."]) ")
73     local code = "print(["..str.."["..code1.."]"..str.."])"
74     --[[ Uncomment to debug
75     if string.match(filename,"vlm_cmd.xml$") then
76     io.write(code)
77     io.write("\n")
78     end
79     --]]
80     return assert(loadstring(code,filename))
81 end
82
83 function process(filename)
84     local mtime = 0    -- vlc.net.stat(filename).modification_time
85     local func = false -- process_raw(filename)
86     return function(...)
87         local new_mtime = vlc.net.stat(filename).modification_time
88         if new_mtime ~= mtime then
89             -- Re-read the file if it changed
90             if mtime == 0 then
91                 vlc.msg.dbg("Loading `"..filename.."'")
92             else
93                 vlc.msg.dbg("Reloading `"..filename.."'")
94             end
95             func = process_raw(filename)
96             mtime = new_mtime
97         end
98         return func(...)
99     end
100 end
101
102
103 function callback_error(path,url,msg)
104     local url = url or "&lt;page unknown&gt;"
105     return  [[<html xmlns="http://www.w3.org/1999/xhtml">
106 <head>
107 <title>Error loading ]]..url..[[</title>
108 </head>
109 <body>
110 <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>
111 <p>
112 <a href="http://www.videolan.org/">VideoLAN</a><br/>
113 <a href="http://www.lua.org/manual/5.1/">Lua 5.1 Reference Manual</a>
114 </p>
115 </body>
116 </html>]]
117 end
118
119 function dirlisting(url,listing,acl_)
120     local list = {}
121     for _,f in ipairs(listing) do
122         if not string.match(f,"^%.") then
123             table.insert(list,"<li><a href='"..f.."'>"..f.."</a></li>")
124         end
125     end
126     list = table.concat(list)
127     local function callback()
128         return [[<html xmlns="http://www.w3.org/1999/xhtml">
129 <head>
130 <title>Directory listing ]]..url..[[</title>
131 </head>
132 <body>
133 <h1>Directory listing ]]..url..[[</h1><ul>]]..list..[[</ul>
134 </body>
135 </html>]]
136     end
137     return h:file(url,"text/html",nil,nil,acl_,callback,nil)
138 end
139
140 -- FIXME: Experimental art support. Needs some cleaning up.
141 function callback_art(data, request)
142     local art = function(data, request)
143         local item = vlc.item()
144         local metas = item:metas()
145         local filename = vlc.strings.decode_uri(string.gsub(metas["artwork_url"],"file://",""))
146         local size = vlc.net.stat(filename).size
147         local ext = string.match(filename,"%.([^%.]-)$")
148         local raw = io.open(filename):read("*a")
149         local content = [[Content-Type: ]]..mimes[ext]..[[
150
151 Content-Length: ]]..size..[[
152
153
154 ]]..raw..[[
155
156 ]]
157         return content
158     end
159
160     local ok, content = pcall(art, data, request)
161     if not ok then
162         return [[Status: 404
163 Content-Type: text/plain
164 Content-Length: 5
165
166 Error
167 ]]
168     end
169     return content
170 end
171
172 function file(h,path,url,acl_,mime)
173     local generate_page = process(path)
174     local callback = function(data,request)
175         -- FIXME: I'm sure that we could define a real sandbox
176         -- redefine print
177         local page = {}
178         local function pageprint(...)
179             for i=1,select("#",...) do
180                 if i== 1 then
181                     table.insert(page,tostring(select(i,...)))
182                 else
183                     table.insert(page," "..tostring(select(i,...)))
184                 end
185             end
186         end
187         _G._GET = parse_url_request(request)
188         local oldprint = print
189         print = pageprint
190         local ok, msg = pcall(generate_page)
191         -- reset
192         print = oldprint
193         if not ok then
194             return callback_error(path,url,msg)
195         end
196         return table.concat(page)
197     end
198     return h:file(url or path,mime,nil,nil,acl_,callback,nil)
199 end
200
201 function rawfile(h,path,url,acl_)
202     local filename = path
203     local mtime = 0    -- vlc.net.stat(filename).modification_time
204     local page = false -- io.open(filename):read("*a")
205     local callback = function(data,request)
206         local new_mtime = vlc.net.stat(filename).modification_time
207         if mtime ~= new_mtime then
208             -- Re-read the file if it changed
209             if mtime == 0 then
210                 vlc.msg.dbg("Loading `"..filename.."'")
211             else
212                 vlc.msg.dbg("Reloading `"..filename.."'")
213             end
214             page = io.open(filename):read("*a")
215             mtime = new_mtime
216         end
217         return page
218     end
219     return h:file(url or path,nil,nil,nil,acl_,callback,nil)
220 end
221
222 function parse_url_request(request)
223     if not request then return {} end
224     local t = {}
225     for k,v in string.gmatch(request,"([^=&]+)=?([^=&]*)") do
226         local k_ = vlc.strings.decode_uri(k)
227         local v_ = vlc.strings.decode_uri(v)
228         if t[k_] ~= nil then
229             local t2
230             if type(t[k_]) ~= "table" then
231                 t2 = {}
232                 table.insert(t2,t[k_])
233                 t[k_] = t2
234             else
235                 t2 = t[k_]
236             end
237             table.insert(t2,v_)
238         else
239             t[k_] = v_
240         end
241     end
242     return t
243 end
244
245 local function find_datadir(name)
246     local list = vlc.misc.datadir_list(name)
247     for _, l in ipairs(list) do
248         local s = vlc.net.stat(l)
249         if s then
250             return l
251         end
252     end
253     error("Unable to find the `"..name.."' directory.")
254 end
255 http_dir = config.dir or find_datadir("http")
256
257 do
258     local oldpath = package.path
259     package.path = http_dir.."/?.lua"
260     local ok, err = pcall(require,"custom")
261     if not ok then
262         vlc.msg.warn("Couldn't load "..http_dir.."/custom.lua",err)
263     else
264         vlc.msg.dbg("Loaded "..http_dir.."/custom.lua")
265     end
266     package.path = oldpath
267 end
268 local files = {}
269 local function load_dir(dir,root,parent_acl)
270     local root = root or "/"
271     local has_index = false
272     local my_acl = parent_acl
273     do
274         local af = dir.."/.hosts"
275         local s = vlc.net.stat(af)
276         if s and s.type == "file" then
277             -- We found an acl
278             my_acl = vlc.acl(false)
279             my_acl:load_file(af)
280         end
281     end
282     local d = vlc.net.opendir(dir)
283     for _,f in ipairs(d) do
284         if not string.match(f,"^%.") then
285             local s = vlc.net.stat(dir.."/"..f)
286             if s.type == "file" then
287                 local url
288                 if f == "index.html" then
289                     url = root
290                     has_index = true
291                 else
292                     url = root..f
293                 end
294                 local ext = string.match(f,"%.([^%.]-)$")
295                 local mime = mimes[ext]
296                 -- print(url,mime)
297                 if mime and string.match(mime,"^text/") then
298                     table.insert(files,file(h,dir.."/"..f,url,my_acl,mime))
299                 else
300                     table.insert(files,rawfile(h,dir.."/"..f,url,my_acl))
301                 end
302             elseif s.type == "dir" then
303                 load_dir(dir.."/"..f,root..f.."/",my_acl)
304             end
305         end
306     end
307     if not has_index and not config.no_index then
308         -- print("Adding index for", root)
309         table.insert(files,dirlisting(root,d,my_acl))
310     end
311     return my_acl
312 end
313
314 local u = vlc.net.url_parse( config.host or "0.0.0.0:8080" )
315 h = vlc.httpd(u.host,u.port)
316 local root_acl = load_dir( http_dir )
317 local a = h:handler("/art",nil,nil,root_acl,callback_art,nil)
318
319 while not vlc.misc.lock_and_wait() do end -- everything happens in callbacks
320