You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
10 KiB
347 lines
10 KiB
--[==========================================================================[
|
|
http.lua: HTTP interface module for VLC
|
|
--[==========================================================================[
|
|
Copyright (C) 2007-2009 the VideoLAN team
|
|
$Id$
|
|
|
|
Authors: Antoine Cellerier <dionoea at videolan dot org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
--]==========================================================================]
|
|
|
|
--[==========================================================================[
|
|
Configuration options:
|
|
* dir: Directory to use as the http interface's root.
|
|
* no_error_detail: If set, do not print the Lua error message when generating
|
|
a page fails.
|
|
* no_index: If set, don't build directory indexes
|
|
--]==========================================================================]
|
|
|
|
|
|
require "common"
|
|
|
|
vlc.msg.info("Lua HTTP interface")
|
|
|
|
open_tag = "<?vlc"
|
|
close_tag = "?>"
|
|
|
|
-- TODO: use internal VLC mime lookup function for mimes not included here
|
|
mimes = {
|
|
txt = "text/plain",
|
|
json = "text/plain",
|
|
html = "text/html",
|
|
xml = "text/xml",
|
|
js = "text/javascript",
|
|
css = "text/css",
|
|
png = "image/png",
|
|
jpg = "image/jpeg",
|
|
jpeg = "image/jpeg",
|
|
ico = "image/x-icon",
|
|
}
|
|
|
|
function escape(s)
|
|
return (string.gsub(s,"([%^%$%%%.%[%]%*%+%-%?])","%%%1"))
|
|
end
|
|
|
|
function my_vlc_load(code, filename)
|
|
if _VERSION == "Lua 5.1" then
|
|
return loadstring(code, filename)
|
|
else
|
|
return load(code, filename)
|
|
end
|
|
end
|
|
|
|
function process_raw(filename)
|
|
local input = io.open(filename):read("*a")
|
|
-- find the longest [===[ or ]=====] type sequence and make sure that
|
|
-- we use one that's longer.
|
|
local str="X"
|
|
for str2 in string.gmatch(input,"[%[%]]=*[%[%]]") do
|
|
if #str < #str2 then str = str2 end
|
|
end
|
|
str=string.rep("=",#str-1)
|
|
|
|
local code0 = string.gsub(input,escape(open_tag),"]"..str.."]) ")
|
|
local code1 = string.gsub(code0,"(%]"..str.."%]%) "..".-)("..escape(close_tag)..")","%1 print(["..str.."[")
|
|
local code = "print(["..str.."["..code1.."]"..str.."])"
|
|
--[[ Uncomment to debug
|
|
if string.match(filename,"vlm_cmd.xml$") then
|
|
io.write(code)
|
|
io.write("\n")
|
|
end
|
|
--]]
|
|
return assert(my_vlc_load(code,filename))
|
|
end
|
|
|
|
function process(filename)
|
|
local mtime = 0 -- vlc.net.stat(filename).modification_time
|
|
local func = false -- process_raw(filename)
|
|
return function(...)
|
|
local new_mtime = vlc.net.stat(filename).modification_time
|
|
if func == false or new_mtime ~= mtime then
|
|
-- Re-read the file if it changed
|
|
if func == false then
|
|
vlc.msg.dbg("Loading `"..filename.."'")
|
|
else
|
|
vlc.msg.dbg("Reloading `"..filename.."'")
|
|
end
|
|
func = process_raw(filename)
|
|
mtime = new_mtime
|
|
end
|
|
return func(...)
|
|
end
|
|
end
|
|
|
|
|
|
-- TODO: print localized error message
|
|
-- For now this relies on lua bindings inappropriately doing so
|
|
local function callback_nopassword()
|
|
return [[Status: 403
|
|
Content-Length: 0
|
|
|
|
]]
|
|
end
|
|
|
|
function callback_error(path,url,msg)
|
|
local url = url or "<page unknown>"
|
|
return [[<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<title>Error loading ]]..url..[[</title>
|
|
</head>
|
|
<body>
|
|
<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 vlc.strings.convert_xml_special_chars(tostring(msg)))..[[</pre>
|
|
<p>
|
|
<a href="https://www.videolan.org/">VideoLAN</a><br/>
|
|
<a href="https://www.lua.org/manual/5.1/">Lua 5.1 Reference Manual</a>
|
|
</p>
|
|
</body>
|
|
</html>]]
|
|
end
|
|
|
|
function dirlisting(url,listing)
|
|
local list = {}
|
|
for _,f in ipairs(listing) do
|
|
if not string.match(f,"^%.") then
|
|
table.insert(list,"<li><a href='"..f.."'>"..f.."</a></li>")
|
|
end
|
|
end
|
|
list = table.concat(list)
|
|
local function callback()
|
|
return [[<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<title>Directory listing ]]..url..[[</title>
|
|
</head>
|
|
<body>
|
|
<h1>Directory listing ]]..url..[[</h1><ul>]]..list..[[</ul>
|
|
</body>
|
|
</html>]]
|
|
end
|
|
return h:file(url,"text/html",nil,password,callback,nil)
|
|
end
|
|
|
|
-- FIXME: Experimental art support. Needs some cleaning up.
|
|
function callback_art(data, request, args)
|
|
local art = function(data, request)
|
|
local num = nil
|
|
if args ~= nil then
|
|
num = string.gmatch(args, "item=(.*)")
|
|
if num ~= nil then
|
|
num = num()
|
|
end
|
|
end
|
|
local pl_item
|
|
if num then
|
|
pl_item = vlc.playlist.get(num)
|
|
else
|
|
pl_item = vlc.playlist.current_item()
|
|
end
|
|
local item = pl_item.item
|
|
local metas = item:metas()
|
|
local filename = vlc.strings.make_path(metas["artwork_url"])
|
|
local size = vlc.net.stat(filename).size
|
|
local ext = string.match(filename,"%.([^%.]-)$")
|
|
local raw = io.open(filename, 'rb'):read("*a")
|
|
local content = [[Content-Type: ]]..(mimes[ext] or "application/octet-stream")..[[
|
|
|
|
Content-Length: ]]..size..[[
|
|
|
|
|
|
]]..raw..[[
|
|
|
|
]]
|
|
return content
|
|
end
|
|
|
|
local ok, content = pcall(art, data, request)
|
|
if not ok then
|
|
return [[Status: 404
|
|
Content-Type: text/plain
|
|
Content-Length: 5
|
|
|
|
Error
|
|
]]
|
|
end
|
|
return content
|
|
end
|
|
|
|
function file(h,path,url,mime)
|
|
local generate_page = process(path)
|
|
local callback = function(data,request)
|
|
-- FIXME: I'm sure that we could define a real sandbox
|
|
-- redefine print
|
|
local page = {}
|
|
local function pageprint(...)
|
|
for i=1,select("#",...) do
|
|
if i== 1 then
|
|
table.insert(page,tostring(select(i,...)))
|
|
else
|
|
table.insert(page," "..tostring(select(i,...)))
|
|
end
|
|
end
|
|
end
|
|
_G._GET = parse_url_request(request)
|
|
local oldprint = print
|
|
print = pageprint
|
|
local ok, msg = pcall(generate_page)
|
|
-- reset
|
|
print = oldprint
|
|
if not ok then
|
|
return callback_error(path,url,msg)
|
|
end
|
|
return table.concat(page)
|
|
end
|
|
return h:file(url or path,mime,nil,password,callback,nil)
|
|
end
|
|
|
|
function rawfile(h,path,url)
|
|
local filename = path
|
|
local mtime = 0 -- vlc.net.stat(filename).modification_time
|
|
local page = false -- io.open(filename):read("*a")
|
|
local callback = function(data,request)
|
|
local new_mtime = vlc.net.stat(filename).modification_time
|
|
if page == false or new_mtime ~= mtime then
|
|
-- Re-read the file if it changed
|
|
if page == false then
|
|
vlc.msg.dbg("Loading `"..filename.."'")
|
|
else
|
|
vlc.msg.dbg("Reloading `"..filename.."'")
|
|
end
|
|
page = io.open(filename,"rb"):read("*a")
|
|
mtime = new_mtime
|
|
end
|
|
return page
|
|
end
|
|
return h:file(url or path,nil,nil,password,callback,nil)
|
|
end
|
|
|
|
function parse_url_request(request)
|
|
if not request then return {} end
|
|
local t = {}
|
|
for k,v in string.gmatch(request,"([^=&]+)=?([^=&]*)") do
|
|
local k_ = vlc.strings.decode_uri(k)
|
|
local v_ = vlc.strings.decode_uri(v)
|
|
if t[k_] ~= nil then
|
|
local t2
|
|
if type(t[k_]) ~= "table" then
|
|
t2 = {}
|
|
table.insert(t2,t[k_])
|
|
t[k_] = t2
|
|
else
|
|
t2 = t[k_]
|
|
end
|
|
table.insert(t2,v_)
|
|
else
|
|
t[k_] = v_
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function find_datadir(name)
|
|
local list = vlc.config.datadir_list(name)
|
|
for _, l in ipairs(list) do
|
|
local s = vlc.net.stat(l)
|
|
if s then
|
|
return l
|
|
end
|
|
end
|
|
error("Unable to find the `"..name.."' directory.")
|
|
end
|
|
http_dir = config.dir or find_datadir("http")
|
|
|
|
do
|
|
local oldpath = package.path
|
|
package.path = http_dir.."/?.lua"
|
|
local ok, err = pcall(require,"custom")
|
|
if not ok then
|
|
vlc.msg.warn("Couldn't load "..http_dir.."/custom.lua",err)
|
|
else
|
|
vlc.msg.dbg("Loaded "..http_dir.."/custom.lua")
|
|
end
|
|
package.path = oldpath
|
|
end
|
|
files = {}
|
|
local function load_dir(dir,root)
|
|
local root = root or "/"
|
|
local has_index = false
|
|
local d = vlc.net.opendir(dir)
|
|
for _,f in ipairs(d) do
|
|
if not string.match(f,"^%.") then
|
|
local s = vlc.net.stat(dir.."/"..f)
|
|
if s.type == "file" then
|
|
local url
|
|
if f == "index.html" then
|
|
url = root
|
|
has_index = true
|
|
else
|
|
url = root..f
|
|
end
|
|
local ext = string.match(f,"%.([^%.]-)$")
|
|
local mime = mimes[ext]
|
|
-- print(url,mime)
|
|
if mime and string.match(mime,"^text/") then
|
|
table.insert(files,file(h,dir.."/"..f,url,mime))
|
|
else
|
|
table.insert(files,rawfile(h,dir.."/"..f,url))
|
|
end
|
|
elseif s.type == "dir" then
|
|
load_dir(dir.."/"..f,root..f.."/")
|
|
end
|
|
end
|
|
end
|
|
if not has_index and not config.no_index then
|
|
-- print("Adding index for", root)
|
|
table.insert(files,dirlisting(root,d))
|
|
end
|
|
end
|
|
|
|
if config.host then
|
|
vlc.msg.err("\""..config.host.."\" HTTP host ignored")
|
|
local port = string.match(config.host, ":(%d+)[^]]*$")
|
|
vlc.msg.info("Pass --http-host=IP "..(port and "and --http-port="..port.." " or "").."on the command line instead.")
|
|
end
|
|
|
|
password = vlc.var.inherit(nil,"http-password")
|
|
|
|
h = vlc.httpd()
|
|
if password == "" then
|
|
vlc.msg.err("Password unset, insecure web interface disabled")
|
|
vlc.msg.info("Set --http-password on the command line if you want to enable the web interface.")
|
|
p = h:handler("/",nil,nil,callback_nopassword,nil)
|
|
else
|
|
load_dir( http_dir )
|
|
a = h:handler("/art",nil,password,callback_art,nil)
|
|
end
|
|
|