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

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

new xupnpd version 1.033

File size: 17.7 KB
Line 
1-- Copyright (C) 2011-2015 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')
87dofile('xupnpd_webapp.lua')
88
89function compile_templates()
90    local path=cfg.tmp_path..'xupnpd-cache'
91
92    os.execute('mkdir -p '..path)
93
94    for i,fname in ipairs(http_templ) do
95        http.compile_template(cfg.www_root..fname,path..fname,http_vars)
96    end
97end
98
99function http_send_headers(err,ext,len)
100    http.send(
101        string.format(
102            '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'..
103            'Connection: close\r\nContent-Type: %s\r\nEXT:\r\n',
104            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')
105    )
106    if len then http.send(string.format("Content-Length: %s\r\n",len)) end
107    http.send("\r\n")
108
109    if cfg.debug>0 and err~=200 then print('http error '..err) end
110
111end
112
113function get_soap_method(s)
114    local i=string.find(s,'#',1,true)
115    if not i then return s end
116    return string.sub(s,i+1)
117end
118
119function plugin_sendurl_from_cache(url,range)
120    local c=cache[url]
121
122    if c==nil or c.value==nil then return false end
123
124    if cfg.debug>0 then print('Cache URL: '..c.value) end
125
126    local rc,location,l
127
128    location=c.value
129
130    for i=1,5,1 do
131        rc,l=http.sendurl(location,1,range)
132
133        if l then
134            location=l
135            core.sendevent('store',url,location)
136            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
137        else
138            if rc~=0 then return true end
139
140            if cfg.debug>0 then print('Retry #'..i..' location: '..location) end
141        end
142    end
143
144    return false
145end
146
147function plugin_sendurl(url,real_url,range)
148    local rc,location,l
149
150    location=real_url
151
152    core.sendevent('store',url,real_url)
153
154    for i=1,5,1 do
155        rc,l=http.sendurl(location,1,range)
156
157        if l then
158            location=l
159            core.sendevent('store',url,location)
160            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
161        else
162            if rc~=0 then return true end
163
164            if cfg.debug>0 then print('Retry #'..i..' location: '..location) end
165        end
166    end
167
168    return false
169end
170
171function plugin_sendfile(path)
172    local len=util.getflen(path)
173    if len then
174        http.send(string.format('Content-Length: %s\r\n\r\n',len))
175        http.sendfile(path)
176    else
177        http.send('\r\n')
178    end
179end
180
181function plugin_download(url)
182    local data,location
183
184    location=url
185
186    for i=1,5,1 do
187        data,location=http.download(location)
188
189        if not location then
190            return data
191        else
192            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
193        end
194    end
195
196    return nil
197end
198
199function plugin_get_length(url)
200    local len,location
201
202    location=url
203
204    for i=1,5,1 do
205        len,location=http.get_length(location)
206
207        if not location then
208            return len
209        else
210            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
211        end
212    end
213
214    return 0
215end
216
217function http_get_action(url)
218
219    local t=split_string(url,'/')
220
221    return t[1] or '', string.match(t[2] or '','^([%w_]+)%.?%w*') or ''
222
223end
224
225local http_ui_main=cfg.ui_path..'xupnpd_ui.lua'
226
227if not util.getflen(http_ui_main) then
228    http_ui_main=nil
229end
230
231function http_handler(what,from,port,msg)
232
233    if not msg or not msg.reqline then return end
234
235    local pr_name=nil
236
237    if cfg.profiles then
238        pr_name=profile_change(msg['user-agent'],msg)
239
240        if msg.reqline[2]=='/dev.xml' then msg.reqline[2]=cfg.dev_desc_xml end
241    end
242
243    if msg.reqline[2]=='/' then
244        if http_ui_main then msg.reqline[2]='/ui' else msg.reqline[2]='/index.html' end
245    end
246
247    local head=false
248
249    local f=util.geturlinfo(cfg.www_root,msg.reqline[2])
250
251    if not f or (msg.reqline[3]~='HTTP/1.0' and msg.reqline[3]~='HTTP/1.1') then
252        http_send_headers(400)
253        return
254    end
255
256    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
257
258    local from_ip=string.match(from,'(.+):.+')
259
260    if string.find(f.url,'^/ui/?') then
261        if not http_ui_main then
262            http_send_headers(404)
263        else
264            dofile(http_ui_main)
265            ui_handler(f.args,msg.data or '',from_ip,f.url)
266        end
267        return
268    elseif string.find(f.url,'^/app/?') then
269        webapp_handler(f.args,msg.data or '',from_ip,f.url)
270        return
271    end
272
273    if msg.reqline[1]=='HEAD' then head=true msg.reqline[1]='GET' end
274
275    local url,object=http_get_action(f.url)
276
277    -- UPnP SOAP Exchange
278    if url=='soap' then
279
280        if not msg.soapaction then msg.soapaction=f.args.action end
281
282        if cfg.debug>0 then print(from..' SOAP '..(msg.soapaction or '')) end
283
284        local err=true
285
286        local s=services[object ]
287
288        if s then
289            local func_name=get_soap_method(msg.soapaction or '')
290            local func=s[func_name]
291
292            if func then
293
294                if cfg.debug>1 then print(msg.data) end
295
296                local r=soap.find('Envelope/Body/'..func_name,soap.parse(msg.data or ''))
297
298                if not r then r=f.args end
299
300                r=func(r or {},from_ip)
301
302                if r then
303                    local resp=
304                        string.format(
305                            '<?xml version=\"1.0\" encoding=\"utf-8\"?>'..
306                            '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">'..
307                            '<s:Body><u:%sResponse xmlns:u=\"%s\">%s</u:%sResponse></s:Body></s:Envelope>',
308                                func_name,s.schema,soap.serialize_vector(r),func_name)
309
310                    local resp_len=resp:len() if cfg.soap_length==false then resp_len=nil end
311
312                    http_send_headers(200,'xml',resp_len)
313
314                    http.send(resp)
315
316                    if cfg.debug>2 then print(resp) end
317
318                    err=false
319                end
320            end
321        end
322
323        if err==true then
324            local resp=
325            '<?xml version=\"1.0\" encoding=\"utf-8\"?>'..
326            '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">'..
327               '<s:Body>'..
328                  '<s:Fault>'..
329                     '<faultcode>s:Client</faultcode>'..
330                     '<faultstring>UPnPError</faultstring>'..
331                     '<detail>'..
332                        '<u:UPnPError xmlns:u=\"urn:schemas-upnp-org:control-1-0\">'..
333                           '<u:errorCode>501</u:errorCode>'..
334                           '<u:errorDescription>Action Failed</u:errorDescription>'..
335                        '</u:UPnPError>'..
336                     '</detail>'..
337                  '</s:Fault>'..
338               '</s:Body>'..
339            '</s:Envelope>'
340
341            local resp_len=resp:len() if cfg.soap_length==false then resp_len=nil end
342
343            http_send_headers(200,'xml',resp_len)
344
345            http.send(resp)
346
347            if cfg.debug>0 then print('upnp error 501') end
348
349        end
350
351    -- UPnP Events
352    elseif url=='event' then
353
354        if msg.reqline[1]=='SUBSCRIBE' then
355            local ttl=cfg.dlna_subscribe_ttl
356            local sid=nil
357            local callback=nil
358
359            if msg.sid then sid=string.match(msg.sid,'uuid:(.+)') else sid=core.uuid() end
360            if msg.callback then callback=string.match(msg.callback,'<(.+)>') end
361
362            if object~='' then
363                core.sendevent('subscribe',object,sid,callback,ttl)
364            end
365
366            http.send(
367                string.format(
368                    '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'..
369                    'Connection: close\r\nEXT:\r\nSID: uuid:%s\r\nTIMEOUT: Second-%d\r\n\r\n',
370                    os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,sid,ttl))
371        elseif msg.reqline[1]=='UNSUBSCRIBE' then
372
373            if msg.sid then
374                core.sendevent('unsubscribe',string.match(msg.sid,'uuid:(.+)'))
375            end
376
377            http.send(
378                string.format(
379                    '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'..
380                    'Connection: close\r\nEXT:\r\n\r\n',
381                    os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server))
382        else
383            http_send_headers(404)
384        end
385
386    -- UPnP Streaming
387    elseif url=='proxy' then
388
389        local pls=find_playlist_object(object)
390
391        if not pls then http_send_headers(404) return end
392
393        local mtype,extras=playlist_item_type(pls)
394
395        http.send(string.format(
396            'HTTP/1.1 200 OK\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\n'..
397            'Connection: close\r\nContent-Type: %s\r\nEXT:\r\n',
398            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,mtype[3]))
399
400        if cfg.dlna_headers==true then http.send('TransferMode.DLNA.ORG: Streaming\r\nContentFeatures.DLNA.ORG: '..extras..'\r\n') end
401
402        if cfg.content_disp==true then
403            http.send(string.format('Content-Disposition: attachment; filename=\"%s.%s\"\r\n',pls.objid,pls.type))      -- string.gsub(pls.name,"[\/#|@&*`']","_")
404        end
405
406        if head==true then
407            http.send('\r\n')
408            http.flush()
409        else
410            if pls.event then core.sendevent(pls.event,pls.url) end
411
412            if cfg.debug>0 then print(from..' PROXY '..pls.url..' <'..mtype[3]..'>') end
413
414            core.sendevent('status',util.getpid(),from_ip..' '..pls.name)
415
416            if pls.plugin then
417                http.send('Accept-Ranges: bytes\r\n')
418                http.flush()
419
420                local p=plugins[pls.plugin]
421
422                if p and p.disabled~=true then p.sendurl(pls.url,msg.range) end
423            else
424                if cfg.wdtv==true then
425                    http.send('Content-Size: 65535\r\n')
426                    http.send('Content-Length: 65535\r\n')
427                end
428
429                http.send('Accept-Ranges: none\r\n\r\n')
430
431                if string.find(pls.url,'^udp://@') then
432                    http.sendmcasturl(string.sub(pls.url,8),cfg.mcast_interface,2048)
433                else
434                    local rc,location
435                    location=pls.url
436                    for i=1,5,1 do
437                        rc,location=http.sendurl(location)
438                        if not location then
439                            break
440                        else
441                            if cfg.debug>0 then print('Redirect #'..i..' to: '..location) end
442                        end
443                    end
444                end
445            end
446        end
447
448    -- UPnP AlbumArt
449    elseif url=='logo' then
450
451        local pls=find_playlist_object(object)
452
453        if not pls or not pls.logo then http_send_headers(404) return end
454
455        http.send(string.format(
456            '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',
457            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime['jpg']))
458
459        if cfg.dlna_headers==true then http.send('ContentFeatures.DLNA.ORG: DLNA.ORG_PN=JPEG_TN\r\n') end
460
461        if head==true then
462            http.send('\r\n')
463        else
464            if cfg.debug>0 then print(from..' LOGO '..pls.logo) end
465
466            http.sendurl(pls.logo,1)
467        end
468
469    -- Subtitle
470    elseif url=='sub' then
471
472        local pls=find_playlist_object(object)
473
474        if not pls or not pls.path then http_send_headers(404) return end
475
476        local path=string.gsub(pls.path,'.%w+$','.srt')
477
478        local flen=util.getflen(path)
479
480        if not flen then http_send_headers(404) return end
481
482        http.send(string.format(
483            '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',
484            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime['srt'],flen))
485
486        if head~=true then
487            if cfg.debug>0 then print(from..' SUB '..path) end
488
489            http.sendfile(path)
490        end
491
492    -- UPnP Local files streaming
493    elseif url=='stream' then
494
495        local pls=find_playlist_object(object)
496
497        if not pls or not pls.path then http_send_headers(404) return end
498
499        local flen=pls.length
500
501        local ffrom=0
502        local flen_total=flen
503
504        if msg.range and flen and flen>0 then
505            local f,t=string.match(msg.range,'bytes=(.*)-(.*)')
506
507            f=tonumber(f)
508            t=tonumber(t)
509
510            if not f then f=0 end
511            if not t then t=flen-1 end
512
513            if f>t or t+1>flen then http_send_headers(416) return end
514
515            ffrom=f
516            flen=t-f+1
517        end
518
519        local mtype,extras=playlist_item_type(pls)
520
521        http.send(string.format(
522            'HTTP/1.1 200 OK\r\nPragma: no-cache\r\nCache-control: no-cache\r\nDate: %s\r\nServer: %s\r\n'..
523            'Connection: close\r\nContent-Type: %s\r\nEXT:\r\n',
524            os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,mtype[3]))
525
526        if flen then
527            http.send(string.format('Accept-Ranges: bytes\r\nContent-Length: %s\r\n',flen))
528        else
529            http.send('Accept-Ranges: none\r\n')
530        end
531
532        if cfg.dlna_headers==true then http.send('TransferMode.DLNA.ORG: Streaming\r\nContentFeatures.DLNA.ORG: '..extras..'\r\n') end
533
534        if cfg.content_disp==true then
535            http.send(string.format('Content-Disposition: attachment; filename=\"%s.%s\"\r\n',pls.objid,pls.type))
536        end
537
538        if msg.range and flen and flen>0 then
539            http.send(string.format('Content-Range: bytes %s-%s/%s\r\n',ffrom,ffrom+flen-1,flen_total))
540        end
541
542        http.send('\r\n')
543        http.flush()
544
545        if head~=true then
546            if pls.event then core.sendevent(pls.event,pls.path) end
547
548            if cfg.debug>0 then print(from..' STREAM '..pls.path..' <'..mtype[3]..'>') end
549
550            core.sendevent('status',util.getpid(),from_ip..' '..pls.name)
551
552            http.sendfile(pls.path,ffrom,flen)
553        end
554
555    else
556        if f.type=='none' then http_send_headers(404) return end
557        if f.type~='file' then http_send_headers(403) return end
558
559        local tmpl_name=nil
560
561        for i,fname in ipairs(http_templ) do
562            if f.url==fname then tmpl_name=cfg.tmp_path..'xupnpd-cache'..fname break end
563        end
564
565        local len=nil
566
567        if not tmpl_name then len=f.length else len=util.getflen(tmpl_name) end
568
569        http.send(
570            string.format(
571                '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',
572                os.date('!%a, %d %b %Y %H:%M:%S GMT'),ssdp_server,http_mime[f.ext] or 'application/x-octet-stream'))
573
574        if len then
575            http.send(string.format('Content-Length: %s\r\n',len))
576        end
577
578        http.send('\r\n')
579
580        if head~=true then
581            if cfg.debug>0 then print(from..' FILE '..f.path) end
582
583            if tmpl_name~=nil then
584                if len then http.sendfile(tmpl_name) else http.sendtfile(f.path,http_vars) end
585            else
586                http.sendfile(f.path)
587            end
588        end
589    end
590
591    http.flush()
592end
593
594compile_templates()
595
596events["http"]=http_handler
597
598http.listen(cfg.http_port,"http")
Note: See TracBrowser for help on using the repository browser.