1 --[==========================================================================[
2 http.lua: HTTP interface module for VLC
3 --[==========================================================================[
4 Copyright (C) 2007-2009 the VideoLAN team
7 Authors: Antoine Cellerier <dionoea at videolan dot org>
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.
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.
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 --]==========================================================================]
24 --[==========================================================================[
25 Configuration options:
26 * dir: Directory to use as the http interface's root.
27 * no_error_detail: If set, do not print the Lua error message when generating
29 * no_index: If set, don't build directory indexes
30 --]==========================================================================]
35 vlc.msg.info("Lua HTTP interface")
40 -- TODO: use internal VLC mime lookup function for mimes not included here
46 js = "text/javascript",
55 return (string.gsub(s,"([%^%$%%%.%[%]%*%+%-%?])","%%%1"))
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.
63 for str2 in string.gmatch(input,"[%[%]]=*[%[%]]") do
64 if #str < #str2 then str = str2 end
66 str=string.rep("=",#str-1)
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.
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
80 return assert(loadstring(code,filename))
83 function process(filename)
84 local mtime = 0 -- vlc.net.stat(filename).modification_time
85 local func = false -- process_raw(filename)
87 local new_mtime = vlc.net.stat(filename).modification_time
88 if new_mtime ~= mtime then
89 -- Re-read the file if it changed
91 vlc.msg.dbg("Loading `"..filename.."'")
93 vlc.msg.dbg("Reloading `"..filename.."'")
95 func = process_raw(filename)
103 function callback_error(path,url,msg)
104 local url = url or "<page unknown>"
105 return [[<html xmlns="http://www.w3.org/1999/xhtml">
107 <title>Error loading ]]..url..[[</title>
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>
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>
119 function dirlisting(url,listing)
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>")
126 list = table.concat(list)
127 local function callback()
128 return [[<html xmlns="http://www.w3.org/1999/xhtml">
130 <title>Directory listing ]]..url..[[</title>
133 <h1>Directory listing ]]..url..[[</h1><ul>]]..list..[[</ul>
137 return h:file(url,"text/html",nil,password,callback,nil)
140 -- FIXME: Experimental art support. Needs some cleaning up.
141 function callback_art(data, request, args)
142 local art = function(data, request)
145 num = string.gmatch(args, "item=(.*)")
152 item = vlc.input.item()
154 item = vlc.playlist.get(num).item
156 local metas = item:metas()
157 local filename = vlc.strings.decode_uri(string.gsub(metas["artwork_url"],"file://",""))
158 local size = vlc.net.stat(filename).size
159 local ext = string.match(filename,"%.([^%.]-)$")
160 local raw = io.open(filename):read("*a")
161 local content = [[Content-Type: ]]..mimes[ext]..[[
163 Content-Length: ]]..size..[[
172 local ok, content = pcall(art, data, request)
175 Content-Type: text/plain
184 function file(h,path,url,mime)
185 local generate_page = process(path)
186 local callback = function(data,request)
187 -- FIXME: I'm sure that we could define a real sandbox
190 local function pageprint(...)
191 for i=1,select("#",...) do
193 table.insert(page,tostring(select(i,...)))
195 table.insert(page," "..tostring(select(i,...)))
199 _G._GET = parse_url_request(request)
200 local oldprint = print
202 local ok, msg = pcall(generate_page)
206 return callback_error(path,url,msg)
208 return table.concat(page)
210 return h:file(url or path,mime,nil,password,callback,nil)
213 function rawfile(h,path,url)
214 local filename = path
215 local mtime = 0 -- vlc.net.stat(filename).modification_time
216 local page = false -- io.open(filename):read("*a")
217 local callback = function(data,request)
218 local new_mtime = vlc.net.stat(filename).modification_time
219 if mtime ~= new_mtime then
220 -- Re-read the file if it changed
222 vlc.msg.dbg("Loading `"..filename.."'")
224 vlc.msg.dbg("Reloading `"..filename.."'")
226 page = io.open(filename,"rb"):read("*a")
231 return h:file(url or path,nil,nil,password,callback,nil)
234 function parse_url_request(request)
235 if not request then return {} end
237 for k,v in string.gmatch(request,"([^=&]+)=?([^=&]*)") do
238 local k_ = vlc.strings.decode_uri(k)
239 local v_ = vlc.strings.decode_uri(v)
242 if type(t[k_]) ~= "table" then
244 table.insert(t2,t[k_])
257 local function find_datadir(name)
258 local list = vlc.config.datadir_list(name)
259 for _, l in ipairs(list) do
260 local s = vlc.net.stat(l)
265 error("Unable to find the `"..name.."' directory.")
267 http_dir = config.dir or find_datadir("http")
270 local oldpath = package.path
271 package.path = http_dir.."/?.lua"
272 local ok, err = pcall(require,"custom")
274 vlc.msg.warn("Couldn't load "..http_dir.."/custom.lua",err)
276 vlc.msg.dbg("Loaded "..http_dir.."/custom.lua")
278 package.path = oldpath
281 local function load_dir(dir,root)
282 local root = root or "/"
283 local has_index = false
284 local d = vlc.net.opendir(dir)
285 for _,f in ipairs(d) do
286 if not string.match(f,"^%.") then
287 local s = vlc.net.stat(dir.."/"..f)
288 if s.type == "file" then
290 if f == "index.html" then
296 local ext = string.match(f,"%.([^%.]-)$")
297 local mime = mimes[ext]
299 if mime and string.match(mime,"^text/") then
300 table.insert(files,file(h,dir.."/"..f,url,mime))
302 table.insert(files,rawfile(h,dir.."/"..f,url))
304 elseif s.type == "dir" then
305 load_dir(dir.."/"..f,root..f.."/")
309 if not has_index and not config.no_index then
310 -- print("Adding index for", root)
311 table.insert(files,dirlisting(root,d))
316 vlc.msg.err("\""..config.host.."\" HTTP host ignored")
317 local port = string.match(config.host, ":(%d+)[^]]*$")
318 vlc.msg.info("Pass --http-host=IP "..(port and "and --http-port="..port.." " or "").."on the command line instead.")
321 password = vlc.var.inherit(nil,"http-password")
324 a = h:handler("/art",nil,password,callback_art,nil)