def process_child(self, ch, name, request): self.debug(f'process_child: {name} [child: {ch}, request: {request}]') if ch is not None: self.info(f'Child found {ch}') if request.method == b'GET' or request.method == b'HEAD': headers = request.getAllHeaders() if b'content-length' in headers: self.warning( f'{request.method} request with content-length ' + f'{headers[b"content-length"]} header - sanitizing') # Fix missing headers for some Samsung TVs (see issue #20) try: del request.received_headers[b'content-length'] except AttributeError: request.received_headers = headers del request.received_headers[b'content-length'] self.debug('data') if len(request.content.getvalue()) > 0: # shall we remove that? # can we remove that? self.warning(f'{request.method} request with ' + f'{len(request.content.getvalue())} ' + f'bytes of message-body - sanitizing') request.content = StringIO() if hasattr(ch, 'location'): self.debug(f'we have a location ' + f'{isinstance(ch.location, resource.Resource)}') if isinstance(ch.location, ReverseProxyResource) or isinstance( ch.location, resource.Resource): # self.info(f'getChild proxy {name} to {ch.location.uri}') self.prepare_connection(request) self.prepare_headers(ch, request) return ch.location try: p = ch.get_path() except TypeError: return self.list_content(name, ch, request) except Exception as msg: self.debug(f'error accessing items path {msg}') self.debug(traceback.format_exc()) return self.list_content(name, ch, request) if p is not None and os.path.exists(p): self.info(f'accessing path {p}') self.prepare_connection(request) self.prepare_headers(ch, request) ch = StaticFile(p) else: self.debug(f'accessing path {p} failed') return self.list_content(name, ch, request) if ch is None: p = util.sibpath(__file__.encode('ascii'), name) self.debug(f'checking if msroot is file: {p}') if os.path.exists(p): ch = StaticFile(p) self.info(f'MSRoot ch {ch}') return ch
def process_child(self, ch, name, request): if ch is not None: self.info('Child found %s', ch) if (request.method == 'GET' or request.method == 'HEAD'): headers = request.getAllHeaders() if 'content-length' in headers: self.warning( '%s request with content-length %s header - sanitizing', request.method, headers['content-length']) del request.received_headers['content-length'] self.debug('data', ) if len(request.content.getvalue()) > 0: """ shall we remove that? can we remove that? """ self.warning( '%s request with %d bytes of message-body - sanitizing', request.method, len(request.content.getvalue())) request.content = StringIO() if hasattr(ch, "location"): self.debug("we have a location %s", isinstance(ch.location, resource.Resource)) if (isinstance(ch.location, ReverseProxyResource) or isinstance(ch.location, resource.Resource)): #self.info('getChild proxy %s to %s' % (name, ch.location.uri)) self.prepare_connection(request) self.prepare_headers(ch, request) return ch.location try: p = ch.get_path() except TypeError: return self.list_content(name, ch, request) except Exception as msg: self.debug("error accessing items path %r", msg) self.debug(traceback.format_exc()) return self.list_content(name, ch, request) if p != None and os.path.exists(p): self.info("accessing path %r", p) self.prepare_connection(request) self.prepare_headers(ch, request) ch = StaticFile(p) else: self.debug("accessing path %r failed", p) return self.list_content(name, ch, request) if ch is None: p = util.sibpath(__file__, name) if os.path.exists(p): ch = StaticFile(p) self.info('MSRoot ch %s', ch) return ch
def render_GET(self, request): self.info(f'render GET {request}') request.setResponseCode(200) if self.contentType is not None: request.setHeader(b'Content-Type', self.contentType) request.write(b'') headers = request.getAllHeaders() if ('connection' in headers and headers['connection'] == 'close'): pass self.start(request) return server.NOT_DONE_YET
def prepare_headers(self, ch, request): request.setHeader('transferMode.dlna.org', request._dlna_transfermode) if hasattr(ch, 'item') and hasattr(ch.item, 'res'): if ch.item.res[0].protocolInfo is not None: additional_info = ch.item.res[0].get_additional_info() if additional_info != '*': request.setHeader('contentFeatures.dlna.org', additional_info) elif 'getcontentfeatures.dlna.org' in request.getAllHeaders(): request.setHeader( 'contentFeatures.dlna.org', "DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000" )
def render_GET(self, request): self.info('render GET %r', request) request.setResponseCode(200) if hasattr(self, 'contentType'): request.setHeader('Content-Type', self.contentType) request.write('') headers = request.getAllHeaders() if ('connection' in headers and headers['connection'] == 'close'): pass self.start(request) return server.NOT_DONE_YET
def render_GET(self, request): self.info(f'render GET {request}') request.setResponseCode(200) if hasattr(self, 'contentType'): request.setHeader(b'Content-Type', self.contentType) request.write(b'') headers = request.getAllHeaders() if ('connection' in headers and headers['connection'] == 'close'): pass if self.requests: if self.streamheader: self.debug('writing streamheader') for h in self.streamheader: request.write(h.data) self.requests.append(request) else: self.parse_pipeline() self.start(request) return server.NOT_DONE_YET
def getChildWithDefault(self, path, request): self.info( f'{self.server.device_type} getChildWithDefault, ' f'{request.method}, {path}, {request.uri} {request.client}' ) headers = request.getAllHeaders() self.debug(f'\t-> headers are: {headers}') if not isinstance(path, bytes): path = path.encode('ascii') if path.endswith(b'\''): self.warning(f'\t modified wrong path from {path} to {path[:-1]}') path = path[:-1] self.debug(f'\t-> path is: {path} [{type(path)}]') try: if ( b'getcontentfeatures.dlna.org' in headers and headers[b'getcontentfeatures.dlna.org'] != b'1' ): request.setResponseCode(400) return static.Data( b'<html><p>wrong value for ' b'getcontentFeatures.dlna.org</p></html>', 'text/html', ) except Exception as e1: self.error(f'MSRoot.getChildWithDefault: {e1}') if request.method == b'HEAD': if b'getcaptioninfo.sec' in headers: self.warning(f'requesting srt file for id {path}') ch = self.store.get_by_id(path) try: location = ch.get_path() caption = ch.caption if caption is None: raise KeyError request.setResponseCode(200) request.setHeader(b'CaptionInfo.sec', caption) return static.Data(b'', 'text/html') except Exception as e2: self.error( f'MSRoot.getChildWithDefault (method: HEAD): {e2}' ) print(traceback.format_exc()) request.setResponseCode(404) return static.Data( b'<html><p>the requested srt file ' b'was not found</p></html>', 'text/html', ) try: request._dlna_transfermode = headers[b'transfermode.dlna.org'] except KeyError: request._dlna_transfermode = b'Streaming' if request.method in (b'GET', b'HEAD'): if COVER_REQUEST_INDICATOR.match(request.uri.decode('utf-8')): self.info(f'request cover for id {path}') def got_item(ch): if ch is not None: request.setResponseCode(200) file = ch.get_cover() if file and os.path.exists(file): self.info(f'got cover {file}') return StaticFile(file) request.setResponseCode(404) return static.Data( b'<html><p>cover requested not found</p></html>', 'text/html', ) dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_item) dfr.isLeaf = True return dfr if ATTACHMENT_REQUEST_INDICATOR.match(request.uri.decode('utf-8')): self.info(f'request attachment {request.args} for id {path}') def got_attachment(ch): try: # FIXME same as below if 'transcoded' in request.args: if ( self.server.coherence.config.get( 'transcoding', 'no' ) == 'yes' ): format = request.args['transcoded'][0] type = request.args['type'][0] self.info( f'request transcoding {format} {type}' ) try: from coherence.transcoder import ( TranscoderManager, ) manager = TranscoderManager( self.server.coherence ) return manager.select( format, ch.item.attachments[ request.args['attachment'][0] ], ) except Exception: self.debug(traceback.format_exc()) request.setResponseCode(404) return static.Data( b'<html><p>the requested transcoded file ' b'was not found</p></html>', 'text/html', ) else: request.setResponseCode(404) return static.Data( b'<html><p>This MediaServer ' b'doesn\'t support transcoding</p></html>', 'text/html', ) else: return ch.item.attachments[ request.args['attachment'][0] ] except Exception: request.setResponseCode(404) return static.Data( b'<html><p>the requested attachment ' b'was not found</p></html>', 'text/html', ) dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_attachment) dfr.isLeaf = True return dfr if request.method in ( b'GET', b'HEAD', ) and TRANSCODED_REQUEST_INDICATOR.match(request.uri.decode('utf-8')): self.info( f'request transcoding to ' f'{request.uri.split(b"/")[-1]} for id {path}' ) if self.server.coherence.config.get('transcoding', 'no') == 'yes': def got_stuff_to_transcode(ch): # FIXME create a generic transcoder class # and sort the details there format = request.uri.split(b'/')[ -1 ] # request.args['transcoded'][0] uri = ch.get_path() try: from coherence.transcoder import TranscoderManager manager = TranscoderManager(self.server.coherence) return manager.select(format, uri) except Exception: self.debug(traceback.format_exc()) request.setResponseCode(404) return static.Data( b'<html><p>the requested transcoded file ' b'was not found</p></html>', 'text/html', ) dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_stuff_to_transcode) dfr.isLeaf = True return dfr request.setResponseCode(404) return static.Data( b'<html><p>This MediaServer ' b'doesn\'t support transcoding</p></html>', 'text/html', ) if request.method == b'POST' and request.uri.endswith(b'?import'): d = self.import_file(path, request) if isinstance(d, defer.Deferred): d.addBoth(self.import_response, path) d.isLeaf = True return d return self.import_response(None, path) if ( b'user-agent' in headers and ( headers[b'user-agent'].find(b'Xbox/') in [0, None] or headers[b'user-agent'].startswith( # XBox # wmp11 b'''Mozilla/4.0 (compatible; UPnP/1.0; Windows''' ) ) and path in [b'description-1.xml', b'description-2.xml'] ): self.info( 'XBox/WMP alert, we need to ' 'simulate a Windows Media Connect server' ) if b'xbox-description-1.xml' in self.children: self.msg('returning xbox-description-1.xml') return self.children[b'xbox-description-1.xml'] # resource http://XXXX/<deviceID>/config # configuration for the given device # accepted methods: # GET, HEAD: # returns the configuration data (in XML format) # POST: stop the current device and restart it # with the posted configuration data if path in (b'config'): backend = self.server.backend backend_type = backend.__class__.__name__ def constructConfigData(backend): msg = '<plugin active="yes">' msg += '<backend>' + to_string(backend_type) + '</backend>' for key, value in list(backend.config.items()): msg += ( '<' + to_string(key) + '>' + to_string(value) + '</' + to_string(key) + '>' ) msg += '</plugin>' return to_bytes(msg) if request.method in (b'GET', b'HEAD'): # the client wants to retrieve the # configuration parameters for the backend msg = constructConfigData(backend) request.setResponseCode(200) return static.Data(msg, 'text/xml') elif request.method in (b'POST'): # the client wants to update the configuration parameters # for the backend we relaunch the backend with the # new configuration (after content validation) def convert_elementtree_to_dict(root): active = False for name, value in list(root.items()): if name == 'active': if value in ('yes'): active = True break if active is False: return None dict = {} for element in root.getchildren(): key = element.tag text = element.text if key != 'backend': dict[key] = text return dict new_config = None try: element_tree = etree.fromstring(request.content.getvalue()) new_config = convert_elementtree_to_dict(element_tree) self.server.coherence.remove_plugin(self.server) self.warning( f'{backend.name} {self.server.device_type} ({backend})' f' with id {str(self.server.uuid)[5:]} deactivated' ) if new_config is None: msg = '<plugin active="no"/>' else: new_backend = self.server.coherence.add_plugin( backend_type, **new_config ) if self.server.coherence.writeable_config(): self.server.coherence.store_plugin_config( new_backend.uuid, new_config ) msg = ( '<html><p>Device restarted. Config file ' + 'has been modified with posted data.</p>' + '</html>' ) # constructConfigData(new_backend) else: msg = ( '<html><p>Device restarted. ' + 'Config file not modified</p>' + '</html>' ) # constructConfigData(new_backend) request.setResponseCode(202) return static.Data(msg.encode('ascii'), 'text/html') except SyntaxError as e: request.setResponseCode(400) return static.Data( f'<html>' f'<p>Invalid data posted:<BR>{e}</p>' f'</html>'.encode('ascii'), 'text/html', ) else: # invalid method requested request.setResponseCode(405) return static.Data( b'<html><p>This resource does not allow ' + b'the requested HTTP method</p></html>', 'text/html', ) if path in self.children: return self.children[path] if request.uri == b'/': return self return self.getChild(path, request)
def getChildWithDefault(self, path, request): self.info('%s getChildWithDefault, %s, %s, %s %s', self.server.device_type, request.method, path, request.uri, request.client) headers = request.getAllHeaders() self.msg(request.getAllHeaders()) try: if headers['getcontentfeatures.dlna.org'] != '1': request.setResponseCode(400) return static.Data( '<html><p>wrong value for getcontentFeatures.dlna.org</p></html>', 'text/html') except: pass if request.method == 'HEAD': if 'getcaptioninfo.sec' in headers: self.warning("requesting srt file for id %s", path) ch = self.store.get_by_id(path) try: location = ch.get_path() caption = ch.caption if caption is None: raise KeyError request.setResponseCode(200) request.setHeader('CaptionInfo.sec', caption) return static.Data('', 'text/html') except: print(traceback.format_exc()) request.setResponseCode(404) return static.Data( '<html><p>the requested srt file was not found</p></html>', 'text/html') try: request._dlna_transfermode = headers['transfermode.dlna.org'] except KeyError: request._dlna_transfermode = 'Streaming' if request.method in ('GET', 'HEAD'): if COVER_REQUEST_INDICATOR.match(request.uri): self.info("request cover for id %s", path) def got_item(ch): if ch is not None: request.setResponseCode(200) file = ch.get_cover() if file and os.path.exists(file): self.info("got cover %s", file) return StaticFile(file) request.setResponseCode(404) return static.Data( '<html><p>cover requested not found</p></html>', 'text/html') dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_item) dfr.isLeaf = True return dfr if ATTACHMENT_REQUEST_INDICATOR.match(request.uri): self.info("request attachment %r for id %s", request.args, path) def got_attachment(ch): try: #FIXME same as below if 'transcoded' in request.args: if self.server.coherence.config.get( 'transcoding', 'no') == 'yes': format = request.args['transcoded'][0] type = request.args['type'][0] self.info("request transcoding %r %r", format, type) try: from coherence.transcoder import TranscoderManager manager = TranscoderManager( self.server.coherence) return manager.select( format, ch.item.attachments[ request.args['attachment'][0]]) except: self.debug(traceback.format_exc()) request.setResponseCode(404) return static.Data( '<html><p>the requested transcoded file was not found</p></html>', 'text/html') else: request.setResponseCode(404) return static.Data( "<html><p>This MediaServer doesn't support transcoding</p></html>", 'text/html') else: return ch.item.attachments[ request.args['attachment'][0]] except: request.setResponseCode(404) return static.Data( '<html><p>the requested attachment was not found</p></html>', 'text/html') dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_attachment) dfr.isLeaf = True return dfr if request.method in ('GET', 'HEAD') and TRANSCODED_REQUEST_INDICATOR.match( request.uri): self.info("request transcoding to %s for id %s", request.uri.split('/')[-1], path) if self.server.coherence.config.get('transcoding', 'no') == 'yes': def got_stuff_to_transcode(ch): #FIXME create a generic transcoder class and sort the details there format = request.uri.split('/')[ -1] # request.args['transcoded'][0] uri = ch.get_path() try: from coherence.transcoder import TranscoderManager manager = TranscoderManager(self.server.coherence) return manager.select(format, uri) except: self.debug(traceback.format_exc()) request.setResponseCode(404) return static.Data( '<html><p>the requested transcoded file was not found</p></html>', 'text/html') dfr = defer.maybeDeferred(self.store.get_by_id, path) dfr.addCallback(got_stuff_to_transcode) dfr.isLeaf = True return dfr request.setResponseCode(404) return static.Data( "<html><p>This MediaServer doesn't support transcoding</p></html>", 'text/html') if request.method == 'POST' and request.uri.endswith('?import'): d = self.import_file(path, request) if isinstance(d, defer.Deferred): d.addBoth(self.import_response, path) d.isLeaf = True return d return self.import_response(None, path) if ('user-agent' in headers and (headers['user-agent'].find('Xbox/') == 0 or # XBox headers['user-agent'].startswith( """Mozilla/4.0 (compatible; UPnP/1.0; Windows""")) and # wmp11 path in ['description-1.xml', 'description-2.xml']): self.info( 'XBox/WMP alert, we need to simulate a Windows Media Connect server' ) if 'xbox-description-1.xml' in self.children: self.msg('returning xbox-description-1.xml') return self.children['xbox-description-1.xml'] # resource http://XXXX/<deviceID>/config # configuration for the given device # accepted methods: # GET, HEAD: returns the configuration data (in XML format) # POST: stop the current device and restart it with the posted configuration data if path in ('config'): backend = self.server.backend backend_type = backend.__class__.__name__ def constructConfigData(backend): msg = "<plugin active=\"yes\">" msg += "<backend>" + backend_type + "</backend>" for key, value in list(backend.config.items()): msg += "<" + key + ">" + value + "</" + key + ">" msg += "</plugin>" return msg if request.method in ('GET', 'HEAD'): # the client wants to retrieve the configuration parameters for the backend msg = constructConfigData(backend) request.setResponseCode(200) return static.Data(msg, 'text/xml') elif request.method in ('POST'): # the client wants to update the configuration parameters for the backend # we relaunch the backend with the new configuration (after content validation) def convert_elementtree_to_dict(root): active = False for name, value in list(root.items()): if name == 'active': if value in ('yes'): active = True break if active is False: return None dict = {} for element in root.getchildren(): key = element.tag text = element.text if key != 'backend': dict[key] = text return dict new_config = None try: element_tree = etree.fromstring(request.content.getvalue()) new_config = convert_elementtree_to_dict(element_tree) self.server.coherence.remove_plugin(self.server) self.warning("%s %s (%s) with id %s desactivated", backend.name, self.server.device_type, backend, str(self.server.uuid)[5:]) if new_config is None: msg = "<plugin active=\"no\"/>" else: new_backend = self.server.coherence.add_plugin( backend_type, **new_config) if self.server.coherence.writeable_config(): self.server.coherence.store_plugin_config( new_backend.uuid, new_config) msg = "<html><p>Device restarted. Config file has been modified with posted data.</p></html>" # constructConfigData(new_backend) else: msg = "<html><p>Device restarted. Config file not modified</p></html>" # constructConfigData(new_backend) request.setResponseCode(202) return static.Data(msg, 'text/html') # 'text/xml') except SyntaxError as e: request.setResponseCode(400) return static.Data( "<html><p>Invalid data posted:<BR>%s</p></html>" % e, 'text/html') else: # invalid method requested request.setResponseCode(405) return static.Data( "<html><p>This resource does not allow the requested HTTP method</p></html>", 'text/html') if path in self.children: return self.children[path] if request.uri == '/': return self return self.getChild(path, request)