source: ipk/source.mipsel/network_xupnpd/_path_/etc/xupnpd/xupnpd_http.lua @ 30367

Last change on this file since 30367 was 30367, checked in by Stephan, 9 years ago

[xupnpd] mipsel version, thanks to ago123

File size: 17.0 KB
Line 
1-- Copyright (C) 2011-2013 Anton Burdinuk
2-- clark15b@gmail.com
3-- https://tsdemuxer.googlecode.com/svn/trunk/xupnpd
4
5http_mime={}
6http_err={}
7http_vars={}
8
9-- http_mime types
10http_mime['html']='text/html'
11http_mime['htm']='text/html'
12http_mime['xml']='text/xml; charset="UTF-8"'
13http_mime['txt']='text/plain'
14http_mime['srt']='video/subtitle'
15http_mime['cpp']='text/plain'
16http_mime['h']='text/plain'
17http_mime['lua']='text/plain'
18http_mime['jpg']='image/jpeg'
19http_mime['png']='image/png'
20http_mime['ico']='image/vnd.microsoft.icon'
21http_mime['mpeg']='video/mpeg'
22http_mime['css']='text/css'
23http_mime['json']='application/json'
24http_mime['js']='application/javascript'
25http_mime['m3u']='audio/x-mpegurl'
26
27-- http http_error list
28http_err[100]='Continue'
29http_err[101]='Switching Protocols'
30http_err[200]='OK'
31http_err[201]='Created'
32http_err[202]='Accepted'
33http_err[203]='Non-Authoritative Information'
34http_err[204]='No Content'
35http_err[205]='Reset Content'
36http_err[206]='Partial Content'
37http_err[300]='Multiple Choices'
38http_err[301]='Moved Permanently'
39http_err[302]='Moved Temporarily'
40http_err[303]='See Other'
41http_err[304]='Not Modified'
42http_err[305]='Use Proxy'
43http_err[400]='Bad Request'
44http_err[401]='Unauthorized'
45http_err[402]='Payment Required'
46http_err[403]='Forbidden'
47http_err[404]='Not Found'
48http_err[405]='Method Not Allowed'
49http_err[406]='Not Acceptable'
50http_err[407]='Proxy Authentication Required'
51http_err[408]='Request Time-Out'
52http_err[409]='Conflict'
53http_err[410]='Gone'
54http_err[411]='Length Required'
55http_err[412]='Precondition Failed'
56http_err[413]='Request Entity Too Large'
57http_err[414]='Request-URL Too Large'
58http_err[415]='Unsupported Media Type'
59http_err[416]='Requested range not satisfiable'
60http_err[500]='Internal Server http_error'
61http_err[501]='Not Implemented'
62http_err[502]='Bad Gateway'
63http_err[503]='Out of Resources'
64http_err[504]='Gateway Time-Out'
65http_err[505]='HTTP Version not supported'
66
67http_vars['fname']=cfg.name
68http_vars['manufacturer']=util.xmlencode('Anton Burdinuk <clark15b@gmail.com>')
69http_vars['manufacturer_url']=''
70http_vars['description']=ssdp_server
71http_vars['name']='xupnpd'
72http_vars['version']=cfg.version
73http_vars['url']='http://xupnpd.org'
74http_vars['uuid']=ssdp_uuid
75http_vars['interface']=ssdp.interface()
76http_vars['port']=cfg.http_port
77http_vars['uptime']=core.uptime
78
79http_templ=
80{
81    '/dev.xml',
82    '/wmc.xml',
83    '/index.html'
84}
85
86dofile('xupnpd_soap.lua')
87
88function compile_templates()
89    local path=cfg.tmp_path..'xupnpd-cache'
90
91    os.execute('mkdir -p '..path)
92
93    for i,fname in ipairs(http_templ) do
94        http.compile_template(cfg.www_root..fname,path..fname,http_vars)
95    end
96end
97
98function http_send_headers(err,ext,len)
99    http.send(
100        string.format(
101            'HTTP/1.1 %i %s\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\nAccept-Ranges: none\r\n'..
102            'Connection: close\r\nContent-Type: %s\r\nEXT:\r\n',
103            err,http_err[err] or 'Unknown',os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime[ext] or 'application/x-octet-stream')
104    )
105    if len then http.send(string.format("Content-Length: %s\r\n",len)) end
106    http.send("\r\n")
107
108    if cfg.debug>0 and err~=200 then print('http error '..err) end
109
110end
111
112function get_soap_method(s)
113    local i=string.find(s,'#',1,true)
114    if not i then return s end
115    return string.sub(s,i+1)
116end
117
118function plugin_sendurl_from_cache(url,range)
119    local c=cache[url]
120
121    if c==nil or c.value==nil then return false end
122
123    if cfg.debug>0 then print('Cache URL: '..c.value) end
124
125    local rc,location,l
126
127    location=c.value
128
129    for i=1,5,1 do
130        rc,l=http.sendurl(location,1,range)
131
132        if l then
133            location=l
134            core.sendevent('store',url,location)
135            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
136        else
137            if rc~=0 then return true end
138
139            if cfg.debug>0 then print('Retry #'..i..' location: '..location) end
140        end
141    end
142
143    return false
144end
145
146function plugin_sendurl(url,real_url,range)
147    local rc,location,l
148
149    location=real_url
150
151    core.sendevent('store',url,real_url)
152
153    for i=1,5,1 do
154        rc,l=http.sendurl(location,1,range)
155
156        if l then
157            location=l
158            core.sendevent('store',url,location)
159            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
160        else
161            if rc~=0 then return true end
162
163            if cfg.debug>0 then print('Retry #'..i..' location: '..location) end
164        end
165    end
166
167    return false
168end
169
170function plugin_sendfile(path)
171    local len=util.getflen(path)
172    if len then
173        http.send(string.format('Content-Length: %s\r\n\r\n',len))
174        http.sendfile(path)
175    else
176        http.send('\r\n')
177    end
178end
179
180function plugin_download(url)
181    local data,location
182
183    location=url
184
185    for i=1,5,1 do
186        data,location=http.download(location)
187
188        if not location then
189            return data
190        else
191            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
192        end
193    end
194
195    return nil
196end
197
198function plugin_get_length(url)
199    local len,location
200
201    location=url
202
203    for i=1,5,1 do
204        len,location=http.get_length(location)
205
206        if not location then
207            return len
208        else
209            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
210        end
211    end
212
213    return 0
214end
215
216function http_get_action(url)
217
218    local t=split_string(url,'/')
219
220    return t[1] or '', string.match(t[2] or '','^([%w_]+)%.?%w*') or ''
221
222end
223
224local http_ui_main=cfg.ui_path..'xupnpd_ui.lua'
225
226if not util.getflen(http_ui_main) then
227    http_ui_main=nil
228end
229
230function http_handler(what,from,port,msg)
231
232    if not msg or not msg.reqline then return end
233
234    local pr_name=nil
235
236    if cfg.profiles then
237        pr_name=profile_change(msg['user-agent'],msg)
238
239        if msg.reqline[2]=='/dev.xml' then msg.reqline[2]=cfg.dev_desc_xml end
240    end
241
242    if msg.reqline[2]=='/' then
243        if http_ui_main then msg.reqline[2]='/ui' else msg.reqline[2]='/index.html' end
244    end
245
246    local head=false
247
248    local f=util.geturlinfo(cfg.www_root,msg.reqline[2])
249
250    if not f or (msg.reqline[3]~='HTTP/1.0' and msg.reqline[3]~='HTTP/1.1') then
251        http_send_headers(400)
252        return
253    end
254
255    if cfg.debug>0 then print(string.format('%s %s %s "%s" [%s]',from,msg.reqline[1],msg.reqline[2],msg['user-agent'] or '',pr_name or 'generic')) end
256
257    local from_ip=string.match(from,'(.+):.+')
258
259    if string.find(f.url,'^/ui/?') then
260        if not http_ui_main then
261            http_send_headers(404)
262        else
263            dofile(http_ui_main)
264            ui_handler(f.args,msg.data or '',from_ip,f.url)
265        end
266        return
267    end
268
269    if msg.reqline[1]=='HEAD' then head=true msg.reqline[1]='GET' end
270
271    local url,object=http_get_action(f.url)
272
273    -- UPnP SOAP Exchange
274    if url=='soap' then
275
276        if not msg.soapaction then msg.soapaction=f.args.action end
277
278        if cfg.debug>0 then print(from..' SOAP '..(msg.soapaction or '')) end
279
280        local err=true
281
282        local s=services[object ]
283
284        if s then
285            local func_name=get_soap_method(msg.soapaction or '')
286            local func=s[func_name]
287
288            if func then
289
290                if cfg.debug>1 then print(msg.data) end
291
292                local r=soap.find('Envelope/Body/'..func_name,soap.parse(msg.data or ''))
293
294                if not r then r=f.args end
295
296                r=func(r or {},from_ip)
297
298                if r then
299                    local resp=
300                        string.format(
301                            '<?xml version=\"1.0\" encoding=\"utf-8\"?>'..
302                            '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">'..
303                            '<s:Body><u:%sResponse xmlns:u=\"%s\">%s</u:%sResponse></s:Body></s:Envelope>',
304                                func_name,s.schema,soap.serialize_vector(r),func_name)
305
306                    local resp_len=resp:len() if cfg.soap_length==false then resp_len=nil end
307
308                    http_send_headers(200,'xml',resp_len)
309
310                    http.send(resp)
311
312                    if cfg.debug>2 then print(resp) end
313
314                    err=false
315                end
316            end
317        end
318
319        if err==true then
320            local resp=
321            '<?xml version=\"1.0\" encoding=\"utf-8\"?>'..
322            '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">'..
323               '<s:Body>'..
324                  '<s:Fault>'..
325                     '<faultcode>s:Client</faultcode>'..
326                     '<faultstring>UPnPError</faultstring>'..
327                     '<detail>'..
328                        '<u:UPnPError xmlns:u=\"urn:schemas-upnp-org:control-1-0\">'..
329                           '<u:errorCode>501</u:errorCode>'..
330                           '<u:errorDescription>Action Failed</u:errorDescription>'..
331                        '</u:UPnPError>'..
332                     '</detail>'..
333                  '</s:Fault>'..
334               '</s:Body>'..
335            '</s:Envelope>'
336
337            local resp_len=resp:len() if cfg.soap_length==false then resp_len=nil end
338
339            http_send_headers(200,'xml',resp_len)
340
341            http.send(resp)
342
343            if cfg.debug>0 then print('upnp error 501') end
344
345        end
346
347    -- UPnP Events
348    elseif url=='event' then
349
350        if msg.reqline[1]=='SUBSCRIBE' then
351            local ttl=1800
352            local sid=core.uuid()
353
354            if object~='' and msg.callback then
355                core.sendevent('subscribe',object,sid,string.match(msg.callback,'<(.+)>'),ttl)
356            end
357
358            http.send(
359                string.format(
360                    'HTTP/1.1 200 OK\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\nAccept-Ranges: none\r\n'..
361                    'Connection: close\r\nEXT:\r\nSID: uuid:%s\r\nTIMEOUT: Second-%d\r\n\r\n',
362                    os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,sid,ttl))
363        elseif msg.reqline[1]=='UNSUBSCRIBE' then
364
365            if msg.sid then
366                core.sendevent('unsubscribe',string.match(msg.sid,'uuid:(.+)'))
367            end
368
369            http.send(
370                string.format(
371                    'HTTP/1.1 200 OK\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\nAccept-Ranges: none\r\n'..
372                    'Connection: close\r\nEXT:\r\n\r\n',
373                    os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server))
374        else
375            http_send_headers(404)
376        end
377
378    -- UPnP Streaming
379    elseif url=='proxy' then
380
381        local pls=find_playlist_object(object)
382
383        if not pls then http_send_headers(404) return end
384
385        local mtype,extras=playlist_item_type(pls)
386
387        http.send(string.format(
388            'HTTP/1.1 200 OK\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\n'..
389            'Connection: close\r\nContent-Type: %s\r\nEXT:\r\n',
390            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,mtype[3]))
391
392        if cfg.dlna_headers==true then http.send('TransferMode.DLNA.ORG: Streaming\r\nContentFeatures.DLNA.ORG: '..extras..'\r\n') end
393
394        if cfg.content_disp==true then
395            http.send(string.format('Content-Disposition: attachment; filename=\"%s.%s\"\r\n',pls.objid,pls.type))      -- string.gsub(pls.name,"[\/#|@&*`']","_")
396        end
397
398        if head==true then
399            http.send('\r\n')
400            http.flush()
401        else
402            if pls.event then core.sendevent(pls.event,pls.url) end
403
404            if cfg.debug>0 then print(from..' PROXY '..pls.url..' <'..mtype[3]..'>') end
405
406            core.sendevent('status',util.getpid(),from_ip..' '..pls.name)
407
408            if pls.plugin then
409                http.send('Accept-Ranges: bytes\r\n')
410                http.flush()
411
412                local p=plugins[pls.plugin]
413
414                if p and p.disabled~=true then p.sendurl(pls.url,msg.range) end
415            else
416                if cfg.wdtv==true then
417                    http.send('Content-Size: 65535\r\n')
418                    http.send('Content-Length: 65535\r\n')
419                end
420
421                http.send('Accept-Ranges: none\r\n\r\n')
422
423                if string.find(pls.url,'^udp://@') then
424                    http.sendmcasturl(string.sub(pls.url,8),cfg.mcast_interface,2048)
425                else
426                    http.sendurl(pls.url)
427                end
428            end
429        end
430
431    -- UPnP AlbumArt
432    elseif url=='logo' then
433
434        local pls=find_playlist_object(object)
435
436        if not pls or not pls.logo then http_send_headers(404) return end
437
438        http.send(string.format(
439            'HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nAccept-Ranges: none\r\nContent-Type: %s\r\nEXT:\r\n',
440            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime['jpg']))
441
442        if cfg.dlna_headers==true then http.send('ContentFeatures.DLNA.ORG: DLNA.ORG_PN=JPEG_TN\r\n') end
443
444        if head==true then
445            http.send('\r\n')
446        else
447            if cfg.debug>0 then print(from..' LOGO '..pls.logo) end
448
449            http.sendurl(pls.logo,1)
450        end
451
452    -- Subtitle
453    elseif url=='sub' then
454
455        local pls=find_playlist_object(object)
456
457        if not pls or not pls.path then http_send_headers(404) return end
458
459        local path=string.gsub(pls.path,'.%w+$','.srt')
460
461        local flen=util.getflen(path)
462
463        if not flen then http_send_headers(404) return end
464
465        http.send(string.format(
466            'HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nAccept-Ranges: none\r\nContent-Type: %s\r\nContent-Length: %s\r\nEXT:\r\n\r\n',
467            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime['srt'],flen))
468
469        if head~=true then
470            if cfg.debug>0 then print(from..' SUB '..path) end
471
472            http.sendfile(path)
473        end
474
475    -- UPnP Local files streaming
476    elseif url=='stream' then
477
478        local pls=find_playlist_object(object)
479
480        if not pls or not pls.path then http_send_headers(404) return end
481
482        local flen=pls.length
483
484        local ffrom=0
485        local flen_total=flen
486
487        if msg.range and flen and flen>0 then
488            local f,t=string.match(msg.range,'bytes=(.*)-(.*)')
489
490            f=tonumber(f)
491            t=tonumber(t)
492
493            if not f then f=0 end
494            if not t then t=flen-1 end
495
496            if f>t or t+1>flen then http_send_headers(416) return end
497
498            ffrom=f
499            flen=t-f+1
500        end
501
502        local mtype,extras=playlist_item_type(pls)
503
504        http.send(string.format(
505            'HTTP/1.1 200 OK\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\n'..
506            'Connection: close\r\nContent-Type: %s\r\nEXT:\r\n',
507            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,mtype[3]))
508
509        if flen then
510            http.send(string.format('Accept-Ranges: bytes\r\nContent-Length: %s\r\n',flen))
511        else
512            http.send('Accept-Ranges: none\r\n')
513        end
514
515        if cfg.dlna_headers==true then http.send('TransferMode.DLNA.ORG: Streaming\r\nContentFeatures.DLNA.ORG: '..extras..'\r\n') end
516
517        if cfg.content_disp==true then
518            http.send(string.format('Content-Disposition: attachment; filename=\"%s.%s\"\r\n',pls.objid,pls.type))
519        end
520
521        if msg.range and flen and flen>0 then
522            http.send(string.format('Content-Range: bytes %s-%s/%s\r\n',ffrom,ffrom+flen-1,flen_total))
523        end
524
525        http.send('\r\n')
526        http.flush()
527
528        if head~=true then
529            if pls.event then core.sendevent(pls.event,pls.path) end
530
531            if cfg.debug>0 then print(from..' STREAM '..pls.path..' <'..mtype[3]..'>') end
532
533            core.sendevent('status',util.getpid(),from_ip..' '..pls.name)
534
535            http.sendfile(pls.path,ffrom,flen)
536        end
537
538    else
539        if f.type=='none' then http_send_headers(404) return end
540        if f.type~='file' then http_send_headers(403) return end
541
542        local tmpl_name=nil
543
544        for i,fname in ipairs(http_templ) do
545            if f.url==fname then tmpl_name=cfg.tmp_path..'xupnpd-cache'..fname break end
546        end
547
548        local len=nil
549
550        if not tmpl_name then len=f.length else len=util.getflen(tmpl_name) end
551
552        http.send(
553            string.format(
554                'HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nAccept-Ranges: none\r\nConnection: close\r\nContent-Type: %s\r\nEXT:\r\n',
555                os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime[f.ext] or 'application/x-octet-stream'))
556
557        if len then
558            http.send(string.format('Content-Length: %s\r\n',len))
559        end
560
561        http.send('\r\n')
562
563        if head~=true then
564            if cfg.debug>0 then print(from..' FILE '..f.path) end
565
566            if tmpl_name~=nil then
567                if len then http.sendfile(tmpl_name) else http.sendtfile(f.path,http_vars) end
568            else
569                http.sendfile(f.path)
570            end
571        end
572    end
573
574    http.flush()
575end
576
577compile_templates()
578
579events["http"]=http_handler
580
581http.listen(cfg.http_port,"http")
Note: See TracBrowser for help on using the repository browser.