1 --[==========================================================================[
2 http.lua: HTTP interface module for VLC
3 --[==========================================================================[
4 Copyright (C) 2007 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 * 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
30 * no_index: If set, don't build directory indexes
31 --]==========================================================================]
36 vlc.msg.info("Lua HTTP interface")
42 return (string.gsub(s,"([%^%$%%%.%[%]%*%+%-%?])","%%%1"))
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.
50 for str2 in string.gmatch(input,"[%[%]]=*[%[%]]") do
51 if #str < #str2 then str = str2 end
53 str=string.rep("=",#str-1)
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.
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
67 return assert(loadstring(code,filename))
69 function process(filename)
70 local mtime = 0 -- vlc.net.stat(filename).modification_time
71 local func = false -- process_raw(filename)
73 local new_mtime = vlc.net.stat(filename).modification_time
74 if new_mtime ~= mtime then
75 -- Re-read the file if it changed
77 vlc.msg.dbg("Loading `"..filename.."'")
79 vlc.msg.dbg("Reloading `"..filename.."'")
81 func = process_raw(filename)
89 function callback_error(path,url,msg)
90 local url = url or "<page unknown>"
91 return [[<html xmlns="http://www.w3.org/1999/xhtml">
93 <title>Error loading ]]..url..[[</title>
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>
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>
105 function dirlisting(url,listing,acl_)
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>")
112 list = table.concat(list)
113 local function callback()
114 return [[<html xmlns="http://www.w3.org/1999/xhtml">
116 <title>Directory listing ]]..url..[[</title>
119 <h1>Directory listing ]]..url..[[</h1><ul>]]..list..[[</ul>
123 return h:file(url,"text/html",nil,nil,acl_,callback,nil)
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
132 local function pageprint(...)
133 for i=1,select("#",...) do
135 table.insert(page,tostring(select(i,...)))
137 table.insert(page," "..tostring(select(i,...)))
141 _G._GET = parse_url_request(request)
142 local oldprint = print
144 local ok, msg = pcall(generate_page)
148 return callback_error(path,url,msg)
150 return table.concat(page)
152 return h:file(url or path,mime,nil,nil,acl_,callback,nil)
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
164 vlc.msg.dbg("Loading `"..filename.."'")
166 vlc.msg.dbg("Reloading `"..filename.."'")
168 page = io.open(filename):read("*a")
173 return h:file(url or path,nil,nil,nil,acl_,callback,nil)
176 function parse_url_request(request)
177 if not request then return {} end
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)
184 if type(t[k_]) ~= "table" then
186 table.insert(t2,t[k_])
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)
207 error("Unable to find the `"..name.."' directory.")
209 http_dir = config.dir or find_datadir("http")
212 local oldpath = package.path
213 package.path = http_dir.."/?.lua"
214 local ok, err = pcall(require,"custom")
216 vlc.msg.warn("Couldn't load "..http_dir.."/custom.lua")
218 vlc.msg.dbg("Loaded "..http_dir.."/custom.lua")
220 package.path = oldpath
227 js = "text/javascript",
230 ico = "image/x-icon",
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
237 local af = dir.."/.hosts"
238 local s = vlc.net.stat(af)
239 if s and s.type == "file" then
241 my_acl = vlc.acl(false)
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
251 if f == "index.html" then
257 local ext = string.match(f,"%.([^%.]-)$")
258 local mime = mimes[ext]
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))
263 table.insert(files,rawfile(h,dir.."/"..f,url,my_acl and my_acl.private))
265 elseif s.type == "dir" then
266 load_dir(dir.."/"..f,root..f.."/",my_acl)
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))
276 local u = vlc.net.url_parse( config.host or "localhost:8080" )
277 h = vlc.httpd(u.host,u.port)
280 while not die do die = vlc.misc.lock_and_wait() end -- everything happens in callbacks
282 -- FIXME: We shouldn't need to do this ourselves.
284 getmetatable(files[i]).__gc(files[i])