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