1 | -- Copyright (C) 2011-2015 Anton Burdinuk |
---|
2 | -- clark15b@gmail.com |
---|
3 | -- https://tsdemuxer.googlecode.com/svn/trunk/xupnpd |
---|
4 | |
---|
5 | http_mime={} |
---|
6 | http_err={} |
---|
7 | http_vars={} |
---|
8 | |
---|
9 | -- http_mime types |
---|
10 | http_mime['html']='text/html' |
---|
11 | http_mime['htm']='text/html' |
---|
12 | http_mime['xml']='text/xml; charset="UTF-8"' |
---|
13 | http_mime['txt']='text/plain' |
---|
14 | http_mime['srt']='video/subtitle' |
---|
15 | http_mime['cpp']='text/plain' |
---|
16 | http_mime['h']='text/plain' |
---|
17 | http_mime['lua']='text/plain' |
---|
18 | http_mime['jpg']='image/jpeg' |
---|
19 | http_mime['png']='image/png' |
---|
20 | http_mime['ico']='image/vnd.microsoft.icon' |
---|
21 | http_mime['mpeg']='video/mpeg' |
---|
22 | http_mime['css']='text/css' |
---|
23 | http_mime['json']='application/json' |
---|
24 | http_mime['js']='application/javascript' |
---|
25 | http_mime['m3u']='audio/x-mpegurl' |
---|
26 | |
---|
27 | -- http http_error list |
---|
28 | http_err[100]='Continue' |
---|
29 | http_err[101]='Switching Protocols' |
---|
30 | http_err[200]='OK' |
---|
31 | http_err[201]='Created' |
---|
32 | http_err[202]='Accepted' |
---|
33 | http_err[203]='Non-Authoritative Information' |
---|
34 | http_err[204]='No Content' |
---|
35 | http_err[205]='Reset Content' |
---|
36 | http_err[206]='Partial Content' |
---|
37 | http_err[300]='Multiple Choices' |
---|
38 | http_err[301]='Moved Permanently' |
---|
39 | http_err[302]='Moved Temporarily' |
---|
40 | http_err[303]='See Other' |
---|
41 | http_err[304]='Not Modified' |
---|
42 | http_err[305]='Use Proxy' |
---|
43 | http_err[400]='Bad Request' |
---|
44 | http_err[401]='Unauthorized' |
---|
45 | http_err[402]='Payment Required' |
---|
46 | http_err[403]='Forbidden' |
---|
47 | http_err[404]='Not Found' |
---|
48 | http_err[405]='Method Not Allowed' |
---|
49 | http_err[406]='Not Acceptable' |
---|
50 | http_err[407]='Proxy Authentication Required' |
---|
51 | http_err[408]='Request Time-Out' |
---|
52 | http_err[409]='Conflict' |
---|
53 | http_err[410]='Gone' |
---|
54 | http_err[411]='Length Required' |
---|
55 | http_err[412]='Precondition Failed' |
---|
56 | http_err[413]='Request Entity Too Large' |
---|
57 | http_err[414]='Request-URL Too Large' |
---|
58 | http_err[415]='Unsupported Media Type' |
---|
59 | http_err[416]='Requested range not satisfiable' |
---|
60 | http_err[500]='Internal Server http_error' |
---|
61 | http_err[501]='Not Implemented' |
---|
62 | http_err[502]='Bad Gateway' |
---|
63 | http_err[503]='Out of Resources' |
---|
64 | http_err[504]='Gateway Time-Out' |
---|
65 | http_err[505]='HTTP Version not supported' |
---|
66 | |
---|
67 | http_vars['fname']=cfg.name |
---|
68 | http_vars['manufacturer']=util.xmlencode('Anton Burdinuk <clark15b@gmail.com>') |
---|
69 | http_vars['manufacturer_url']='' |
---|
70 | http_vars['description']=ssdp_server |
---|
71 | http_vars['name']='xupnpd' |
---|
72 | http_vars['version']=cfg.version |
---|
73 | http_vars['url']='http://xupnpd.org' |
---|
74 | http_vars['uuid']=ssdp_uuid |
---|
75 | http_vars['interface']=ssdp.interface() |
---|
76 | http_vars['port']=cfg.http_port |
---|
77 | http_vars['uptime']=core.uptime |
---|
78 | |
---|
79 | http_templ= |
---|
80 | { |
---|
81 | '/dev.xml', |
---|
82 | '/wmc.xml', |
---|
83 | '/index.html' |
---|
84 | } |
---|
85 | |
---|
86 | dofile('xupnpd_soap.lua') |
---|
87 | dofile('xupnpd_webapp.lua') |
---|
88 | |
---|
89 | function 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 |
---|
97 | end |
---|
98 | |
---|
99 | function 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 | |
---|
111 | end |
---|
112 | |
---|
113 | function 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) |
---|
117 | end |
---|
118 | |
---|
119 | function 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 |
---|
145 | end |
---|
146 | |
---|
147 | function 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 |
---|
169 | end |
---|
170 | |
---|
171 | function 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 |
---|
179 | end |
---|
180 | |
---|
181 | function 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 |
---|
197 | end |
---|
198 | |
---|
199 | function 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 |
---|
215 | end |
---|
216 | |
---|
217 | function 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 | |
---|
223 | end |
---|
224 | |
---|
225 | local http_ui_main=cfg.ui_path..'xupnpd_ui.lua' |
---|
226 | |
---|
227 | if not util.getflen(http_ui_main) then |
---|
228 | http_ui_main=nil |
---|
229 | end |
---|
230 | |
---|
231 | function 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() |
---|
592 | end |
---|
593 | |
---|
594 | compile_templates() |
---|
595 | |
---|
596 | events["http"]=http_handler |
---|
597 | |
---|
598 | http.listen(cfg.http_port,"http") |
---|