source: ipk/source.sh4/network_xupnpd/_path_/etc/xupnpd/xupnpd_http.lua

Last change on this file was 34402, checked in by Stephan, 11 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.