1 | -- Copyright (C) 2011-2013 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 | |
---|
88 | function 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 |
---|
96 | end |
---|
97 | |
---|
98 | function 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 | |
---|
110 | end |
---|
111 | |
---|
112 | function 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) |
---|
116 | end |
---|
117 | |
---|
118 | function 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 |
---|
144 | end |
---|
145 | |
---|
146 | function 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 |
---|
168 | end |
---|
169 | |
---|
170 | function 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 |
---|
178 | end |
---|
179 | |
---|
180 | function 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 |
---|
196 | end |
---|
197 | |
---|
198 | function 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 |
---|
214 | end |
---|
215 | |
---|
216 | function 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 | |
---|
222 | end |
---|
223 | |
---|
224 | local http_ui_main=cfg.ui_path..'xupnpd_ui.lua' |
---|
225 | |
---|
226 | if not util.getflen(http_ui_main) then |
---|
227 | http_ui_main=nil |
---|
228 | end |
---|
229 | |
---|
230 | function 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() |
---|
575 | end |
---|
576 | |
---|
577 | compile_templates() |
---|
578 | |
---|
579 | events["http"]=http_handler |
---|
580 | |
---|
581 | http.listen(cfg.http_port,"http") |
---|