| 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")
|
|---|