File: http.lua

package info (click to toggle)
vlc 3.0.23-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 208,024 kB
  • sloc: ansic: 443,448; cpp: 111,223; objc: 36,399; sh: 6,737; makefile: 6,627; javascript: 4,902; xml: 1,611; asm: 1,355; yacc: 644; python: 321; lex: 88; perl: 77; sed: 16
file content (345 lines) | stat: -rw-r--r-- 10,722 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
--[==========================================================================[
 http.lua: HTTP interface module for VLC
--[==========================================================================[
 Copyright (C) 2007-2009 the VideoLAN team

 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
--]==========================================================================]


local common = 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 "&lt;page unknown&gt;"
    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="http://www.videolan.org/">VideoLAN</a><br/>
<a href="http://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 item
        if num == nil then
            item = vlc.input.item()
        else
            item = vlc.playlist.get(num).item
        end
        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