def __init__(self, service, subscription_duration, delivery_url, http_version, event_reload_time, force_event_reload): self.service = service self.subscription_id = uuid.uuid4() self.delivery_url = delivery_url url = parse_url(delivery_url) self.host = '%s:%d' % (url.hostname, url.port) self.event_key = 0 self.subscription_duration = subscription_duration self.http_version = http_version self.timestamp = datetime.now() self.eventing_variables = {} for name, state_var in self.service.get_variables().items(): state_var.subscribe_for_update(self._update_variable) self.force_event_reload = force_event_reload if not force_event_reload: self.looping_call = LoopingCall(self._send_variables) reactor.add_after_stop_func(self.looping_call.stop) self.looping_call.start(event_reload_time, False) sid = str(self.subscription_id) log.debug('Creating subscriber with subscription id: %s' % sid)
def __init__(self, service, event_host, callback, cargo): """ Constructor for the UnsubscribeRequest class. @param service: service that is unsubscribing @param event_host: 2-tuple (host, port) of the event listener server @param callback: callback @param cargo: callback parameters @type service: Service @type event_host: tuple @type callback: callable """ self.old_sid = service.event_sid service.event_sid = "" service.event_timeout = 0 self.callback = callback self.cargo = cargo self.service = service addr = "%s%s" % (service.url_base, service.event_sub_url) Paddr = parse_url(addr) headers = {} headers["User-agent"] = 'BRISA-CP' headers["HOST"] = '%s:%d' % (Paddr.hostname, Paddr.port) headers["SID"] = self.old_sid run_async_call(http_call, success_callback=self.response, error_callback=self.error, delay=0, method='UNSUBSCRIBE', url=addr, headers=headers)
def __init__(self, service, event_host, callback, cargo): """ Constructor for the SubscribeRequest class. @param service: service that is subscribing @param event_host: 2-tuple (host, port) of the event listener server @param callback: callback @param cargo: callback parameters @type service: Service @type event_host: tuple @type callback: callable """ log.debug("subscribe") self.callback = callback self.cargo = cargo self.service = service addr = "%s%s" % (service.url_base, service.event_sub_url) Paddr = parse_url(addr) headers = {} # headers["Host"] = Paddr.hostname headers["User-agent"] = 'BRisa UPnP Framework' headers["TIMEOUT"] = 'Second-1800' headers["NT"] = 'upnp:event' headers["CALLBACK"] = "<http://%s:%d/eventSub>" % event_host headers["HOST"] = '%s:%d' % (Paddr.hostname, Paddr.port) run_async_call(http_call, success_callback=self.response, error_callback=self.error, delay=0, method='SUBSCRIBE', url=addr, headers=headers)
def getAlbumArt(service, AlbumArtURI): #TODO: display a "getting album art" graphic until art is updated # log.debug('#### GETALBUMART service: %s' % service) # log.debug('#### GETALBUMART AlbumArtURI: %s' % AlbumArtURI) if AlbumArtURI == '' or AlbumArtURI == None: return (None, "No albumart to display") else: url_info = parse_url(service.url_base) # log.debug('#### GETALBUMART url_info: %s' % str(url_info)) aa_url = "%s://%s%s" % (url_info[0], url_info[1], AlbumArtURI) # log.debug('#### GETALBUMART control_url: %s' % aa_url) try: fd = url_fetch(aa_url) # except HTTPError as detail: except HTTPError: return (None, HTTPError) try: data = fd.read() except: log.debug("#### GETALBUMART fd is invalid") return (None, "Error getting albumArt") pbl = gtk.gdk.PixbufLoader() pbl.write(data) pbuf = pbl.get_pixbuf() # print "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" # print pbl.get_format() # print "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" pbl.close() return (pbuf, '')
def _parse_device(self): self.device.device_type = self.tree.\ findtext('.//{%s}deviceType' % self.ns) self.device.friendly_name = self.tree.\ findtext('.//{%s}friendlyName' % self.ns) self.device.manufacturer = self.tree.\ findtext('.//{%s}manufacturer' % self.ns) self.device.manufacturer_url = self.tree.\ findtext('.//{%s}manufacturerURL' % self.ns) self.device.model_description = self.tree.\ findtext('.//{%s}modelDescription' % self.ns) self.device.model_name = self.tree.\ findtext('.//{%s}modelName' % self.ns) self.device.model_number = self.tree.\ findtext('.//{%s}modelNumber' % self.ns) self.device.model_url = self.tree.\ findtext('.//{%s}modelURL' % self.ns) self.device.serial_number = self.tree.\ findtext('.//{%s}serialNumber' % self.ns) self.device.udn = self.tree.findtext('.//{%s}UDN' % self.ns) self.device.upc = self.tree.findtext('.//{%s}UPC' % self.ns) self.device.presentation_url = self.tree.\ findtext('.//{%s}presentationURL' % self.ns) self.device.location = self.location addr = parse_url(self.location) self.device.address = '%s://%s:%d' % (addr.scheme, addr.hostname, addr.port) self.device.scheme = addr.scheme self.device.ip = addr.hostname self.device.port = addr.port
def __init__(self, service, event_host, callback, cargo): """ Constructor for the SubscribeRequest class. @param service: service that is subscribing @param event_host: 2-tuple (host, port) of the event listener server @param callback: callback @param cargo: callback parameters @type service: Service @type event_host: tuple @type callback: callable """ log.debug("subscribe") self.callback = callback self.cargo = cargo self.service = service addr = "%s%s" % (service.url_base, service.event_sub_url) Paddr = parse_url(addr) headers = {} headers["User-agent"] = 'BRisa UPnP Framework' headers["TIMEOUT"] = 'Second-300' headers["NT"] = 'upnp:event' headers["CALLBACK"] = "<http://%s:%d/eventSub>" % event_host headers["HOST"] = '%s:%d' % (Paddr.hostname, Paddr.port) run_async_call(http_call, success_callback=self.response, error_callback=self.error, delay=0, method='SUBSCRIBE', url=addr, headers=headers)
def __init__(self, service, event_host, callback, cargo): """ Constructor for the RenewSubscribeRequest class. @param service: service that is renewing the subscribe @param event_host: 2-tuple (host, port) of the event listener server @param callback: callback @param cargo: callback parameters @type service: Service @type event_host: tuple @type callback: callable """ log.debug("renew subscribe") if not service.event_sid or service.event_sid == "": return self.callback = callback self.cargo = cargo self.service = service addr = "%s%s" % (service.url_base, service.event_sub_url) Paddr = parse_url(addr) headers = {} headers["HOST"] = '%s:%d' % (Paddr.hostname, Paddr.port) headers["SID"] = self.service.event_sid headers["TIMEOUT"] = 'Second-300' run_async_call(http_call, success_callback=self.response, error_callback=self.error, delay=0, method='SUBSCRIBE', url=addr, headers=headers)
def _create_webserver(self, force_listen_url=''): if force_listen_url: p = network.parse_url(force_listen_url) self.webserver = webserver.WebServer(host=p.hostname, port=p.port) else: self.webserver = webserver.WebServer() self.location = self.webserver.get_listen_url()
def getAlbumArtURL(service, albumartURI): if albumartURI == '' or albumartURI == None or albumartURI == []: return '' else: art_url = urlparse(albumartURI) if art_url.scheme != '' and art_url.netloc != '' and art_url.path != '': return albumartURI url_info = parse_url(service.url_base) aa_url = "%s://%s%s" % (url_info[0], url_info[1], albumartURI) return aa_url
def get_music_services(self): """ """ service = self.get_ms_service() service_response = service.ListAvailableServices() if 'AvailableServiceDescriptorList' in service_response: ''' <Services> <Service Capabilities="31" Id="0" MaxMessagingChars="0" Name="Napster" SecureUri="https://api.napster.com/device/soap/v1" Uri="http://api.napster.com/device/soap/v1" Version="1.0"> <Policy Auth="UserId" PollInterval="30"/> <Presentation> <Strings Uri="http://update-services.sonos.com/services/napster/string.xml" Version="1"/> <Logos Large="http://www.napster.com/services/Sonos/LargeLogo.png" Small="http://www.napster.com/services/Sonos/SmallLogo.png"/> </Presentation> </Service> <Service Capabilities="0" Id="254" MaxMessagingChars="0" Name="RadioTime" SecureUri="http://legato.radiotime.com/Radio.asmx" Uri="http://legato.radiotime.com/Radio.asmx" Version="1.1"> <Policy Auth="Anonymous" PollInterval="0"/> <Presentation/> </Service> <Service Capabilities="19" Id="2" MaxMessagingChars="0" Name="Deezer" SecureUri="https://moapi.sonos.com/Deezer/SonosAPI.php" Uri="http://moapi.sonos.com/Deezer/SonosAPI.php" Version="1.1"> <Policy Auth="UserId" PollInterval="60"/> <Presentation/> </Service> </Services> ''' elt = AvailableServices().from_string(service_response['AvailableServiceDescriptorList']) service_response['AvailableServiceDescriptorList'] = elt.get_items() for item in service_response['AvailableServiceDescriptorList']: # manually create Sonos specific add-on services serviceversion = 'http://www.sonos.com/Services/' + item.Version # TODO: fix params so they are fully dynamic if item.Name == 'RadioTime': addr = parse_url(item.Uri) port = 80 if addr.port == None else addr.port url_base = '%s://%s:%s' % (addr.scheme, addr.hostname, port) self.rt_service = Service('getMetadata', serviceversion, url_base=url_base, control_url=addr.path, scpd_url='file:///radiotime-scpd.xml', build=True) elif item.Name == 'Napster': self.np_service = napster(item.SecureUri, item.Uri, serviceversion) return service_response
def getscpdurl(self, location): ''' Gets the scpd url from a url specifying the device xml location ''' addr = parse_url(location) base_url = '%s://%s' % (addr.scheme, addr.netloc) # print "---> base_url: " + str(base_url) filecontent = url_fetch(location) if not filecontent: return None tree = ElementTree(file=filecontent).getroot() for device in tree.findall('{urn:schemas-upnp-org:device-1-0}device'): for xml_service_element in device.findall('.//{urn:schemas-upnp-org:device-1-0}service'): service_type = xml_service_element.findtext('{urn:schemas-upnp-org:device-1-0}serviceType') if service_type == 'urn:schemas-upnp-org:service:ContentDirectory:1': scpd_url = xml_service_element.findtext('{urn:schemas-upnp-org:device-1-0}SCPDURL') if not scpd_url.startswith('/'): scpd_url = '/' + scpd_url scpd_url = base_url + scpd_url return scpd_url return None
def getAlbumArtFile(service, AlbumArtURI, filename): # log.debug('#### GETALBUMART service: %s' % service) # log.debug('#### GETALBUMART AlbumArtURI: %s' % AlbumArtURI) if AlbumArtURI == '' or AlbumArtURI == None: return False else: url_info = parse_url(service.url_base) aa_url = "%s://%s%s" % (url_info[0], url_info[1], AlbumArtURI) # log.debug('#### GETALBUMART control_url: %s' % aa_url) fd = url_fetch(aa_url) try: data = fd.read() except: log.debug("#### GETALBUMART fd is invalid") data = '' fdout = open(filename, "w") fdout.write(data) fdout.close() return True
def getAlbumArtFile(service, AlbumArtURI, filename): # log.debug('#### GETALBUMART service: %s' % service) # log.debug('#### GETALBUMART AlbumArtURI: %s' % AlbumArtURI) if AlbumArtURI == '' or AlbumArtURI == None: return False else: url_info = parse_url(service.url_base) aa_url = "%s://%s%s" % (url_info[0], url_info[1], AlbumArtURI) # log.debug('#### GETALBUMART control_url: %s' % aa_url) fd = url_fetch(aa_url) try: data = fd.read() except: log.debug("#### GETALBUMART fd is invalid") data = '' fdout = open(filename,"w") fdout.write(data) fdout.close() return True
def __init__(self, service, event_host, callback, cargo): """ Constructor for the RenewSubscribeRequest class. @param service: service that is renewing the subscribe @param event_host: 2-tuple (host, port) of the event listener server @param callback: callback @param cargo: callback parameters @type service: Service @type event_host: tuple @type callback: callable """ log.debug("renew subscribe") if not service.event_sid or service.event_sid == "": return self.callback = callback self.cargo = cargo self.service = service addr = "%s%s" % (service.url_base, service.event_sub_url) Paddr = parse_url(addr) headers = {} headers["HOST"] = '%s:%d' % (Paddr.hostname, Paddr.port) headers["SID"] = self.service.event_sid headers["TIMEOUT"] = 'Second-1800' run_async_call(http_call, success_callback=self.response, error_callback=self.error, delay=0, method='SUBSCRIBE', url=addr, headers=headers)
def call(self, addr, data, namespace, soapaction=None, encoding=None): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @param data: data to be sent @param soapaction: soap action to be called @param encoding: encoding for the message @type addr: string @type data: string @type soapaction: string @type encoding: string @return: response payload @rtype: string """ log.debug('#### HTTPTransport call - addr : %s' % str(addr)) log.debug('#### HTTPTransport call - data : %s' % str(data)) log.debug('#### HTTPTransport call - namespace : %s' % str(namespace)) log.debug('#### HTTPTransport call - soapaction : %s' % str(soapaction)) log.debug('#### HTTPTransport call - encoding : %s' % str(encoding)) # Build a request ''' addr : http://legato.radiotime.com:80 data : <?xml version="1.0" encoding="utf-8"?><s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><credentials xmlns="http://www.sonos.com/Services/1.1"><deviceProvider>Sonos</deviceProvider></credentials></s:Header><s:Body><ns0:getMetadata xmlns:ns0="http://www.sonos.com/Services/1.1"><count>100</count><index>0</index><recursive>false</recursive><id>root</id></ns0:getMetadata></s:Body></s:Envelope> namespace : ('u', 'http://www.sonos.com/Services/1.1') soapaction : http://www.sonos.com/Services/1.1#getMetadata encoding : utf-8 real_addr : legato.radiotime.com:80 real_path : addr.scheme : http addr.hostname : legato.radiotime.com POST /Radio.asmx HTTP/1.1 CONNECTION: close ACCEPT-ENCODING: gzip HOST: legato.radiotime.com USER-AGENT: Linux UPnP/1.0 Sonos/11.7-19141a CONTENT-LENGTH: 337 CONTENT-TYPE: text/xml; charset="utf-8" ACCEPT-LANGUAGE: en-US SOAPACTION: "http://www.sonos.com/Services/1.1#getMetadata" ''' # TODO: tidy up parameters, use saved params from musicservices call, change to gzip addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.scheme == 'https': r = httplib.HTTPSConnection(real_addr) else: r = httplib.HTTPConnection(real_addr) log.debug('#### HTTPTransport call - real_addr : %s' % real_addr) log.debug('#### HTTPTransport call - real_path : %s' % real_path) log.debug('#### HTTPTransport call - addr.scheme : %s' % addr.scheme) log.debug('#### HTTPTransport call - addr.hostname : %s' % addr.hostname) r.putrequest("POST", real_path, skip_host=1, skip_accept_encoding=1) r.putheader("ACCEPT-ENCODING", 'gzip') r.putheader("CONNECTION", 'close') r.putheader("HOST", addr.hostname) r.putheader("USER-AGENT", 'Linux UPnP/1.0 Sonos/11.7-19141a') t = 'text/xml' if encoding: t += '; charset="%s"' % encoding r.putheader("CONTENT-TYPE", t) # r.putheader("ACCEPT-CHARSET", 'ISO-8859-1,utf-8;q=0.7,*;q=0.7') r.putheader("ACCEPT-LANGUAGE", 'en-US') r.putheader("CONTENT-LENGTH", str(len(data))) # if user is not a user:passwd format if addr.username != None: val = base64.encodestring(addr.user) r.putheader('Authorization', 'Basic ' + val.replace('\012', '')) # This fixes sending either "" or "None" if soapaction: r.putheader("SOAPACTION", '"%s"' % soapaction) else: r.putheader("SOAPACTION", "") r.endheaders() log.debug('#### HTTP BEFORE r.send ################################') r.send(data) log.debug('#### HTTP AFTER r.send ################################') #read response line # code, msg, headers = r.getreply() response = r.getresponse() code = response.status msg = response.reason headers = response.msg log.debug('#### HTTP AFTER START #################################') log.debug('#### HTTP code : %s' % str(code)) log.debug('#### HTTP msg : %s' % str(msg)) log.debug('#### HTTP headers : %s' % str(headers)) log.debug('#### HTTP AFTER END ###################################') content_type = headers.get("content-type", "text/xml") content_length = headers.get("Content-length") if content_length == None: # data = r.getfile().read() data = response.read() message_len = len(data) else: message_len = int(content_length) # data = r.getfile().read(message_len) data = response.read(message_len) def startswith(string, val): return string[0:len(val)] == val if code == 500 and not \ (startswith(content_type, "text/xml") and message_len > 0): raise HTTPError(code, msg) if code not in (200, 500): raise HTTPError(code, msg) import StringIO stream = StringIO.StringIO(data) import gzip gzipper = gzip.GzipFile(fileobj=stream) data = gzipper.read() # TODO: use the content-type charset to convert the data returned #return response payload # NAS is sending some non utf-8 data - TODO: fix NAS rather than decoding for all types which is redundant try: d = data.decode('utf-8', 'replace') except UnicodeDecodeError: print "UnicodeDecodeError" return data log.debug('#### HTTP data : %s' % d) return d
def call(self, addr, environ, start_response): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @type addr: string @return: response payload @rtype: string """ log.debug('#### HTTPProxy call - addr : %s' % str(addr)) # Build a request addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.query: real_path += '?' + addr.query if addr.scheme == 'https': r = httplib.HTTPSConnection(real_addr) else: r = httplib.HTTPConnection(real_addr) log.debug('#### HTTPProxy call - real_addr : %s' % real_addr) log.debug('#### HTTPProxy call - real_path : %s' % real_path) log.debug('#### HTTPProxy call - addr.scheme : %s' % addr.scheme) log.debug('#### HTTPProxy call - addr.hostname : %s' % addr.hostname) headers = {} for key, value in environ.items(): if key.startswith('HTTP_'): key = key[5:].lower().replace('_', '-') if key == 'host': continue headers[key] = value headers['host'] = real_addr log.debug('#### HTTPProxy headers: %s' % str(headers)) if 'range' in headers: # second request, return everything send_all = True else: send_all = False if 'REMOTE_ADDR' in environ: headers['x-forwarded-for'] = environ['REMOTE_ADDR'] if environ.get('CONTENT_TYPE'): headers['content-type'] = environ['CONTENT_TYPE'] if environ.get('CONTENT_LENGTH'): headers['content-length'] = environ['CONTENT_LENGTH'] length = int(environ['CONTENT_LENGTH']) body = environ['wsgi.input'].read(length) else: body = '' path_info = urllib.quote(environ['PATH_INFO']) if real_path: request_path = path_info if request_path[0] == '/': request_path = request_path[1:] path = urlparse.urljoin(real_path, request_path) else: path = path_info if environ.get('QUERY_STRING'): path += '?' + environ['QUERY_STRING'] log.debug('#### HTTPProxy BEFORE r.request ################################') r.request(environ['REQUEST_METHOD'], path, body, headers) log.debug('#### HTTPProxy BEFORE r.getresponse ################################') res = r.getresponse() log.debug('#### HTTPProxy AFTER r.getresponse ################################') log.debug('#### HTTPProxy AFTER r.getresponse res: %s', res) headers_out = parse_headers(res.msg) log.debug('#### HTTPProxy headers_out: %s' % str(headers_out)) status = '%s %s' % (res.status, res.reason) log.debug('#### HTTPProxy status: %s' % str(status)) start_response(status, headers_out) # this is for the original GET from the ZP if send_all: # length = 1000000 length = res.getheader('content-length') else: length = 400000 if length is not None: body = res.read(int(length)) else: body = res.read() r.close() # TODO: decode utf-8 return body
def parse_base_url(url): if url == '': return '' parsed = parse_url(url) return '%s://%s' % (parsed[0], parsed[1])
def parse_base_url(url): parsed = parse_url(url) return '%s://%s' % (parsed[0], parsed[1])
def call(self, addr, data, namespace, soapaction=None, encoding=None): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @param data: data to be sent @param soapaction: soap action to be called @param encoding: encoding for the message @type addr: string @type data: string @type soapaction: string @type encoding: string @return: response payload @rtype: string """ # Build a request addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.query != '': real_path += '?%s' % addr.query if addr.scheme == 'https': r = httplib.HTTPS(real_addr) else: r = httplib.HTTP(real_addr) r.putrequest("POST", real_path) r.putheader("Host", addr.hostname) r.putheader("User-agent", USER_AGENT) t = 'text/xml' if encoding: t += '; charset="%s"' % encoding r.putheader("Content-type", t) r.putheader("Content-length", str(len(data))) # if user is not a user:passwd format if addr.username != None: val = base64.encodestring(addr.user) r.putheader('Authorization', 'Basic ' + val.replace('\012', '')) # This fixes sending either "" or "None" if soapaction: r.putheader("SOAPAction", '"%s"' % soapaction) else: r.putheader("SOAPAction", "") r.endheaders() r.send(data) #read response line code, msg, headers = r.getreply() content_type = headers.get("content-type", "text/xml") content_length = headers.get("Content-length") if content_length == None: data = r.getfile().read() message_len = len(data) else: message_len = int(content_length) data = r.getfile().read(message_len) def startswith(string, val): return string[0:len(val)] == val if code == 500 and not \ (startswith(content_type, "text/xml") and message_len > 0): raise HTTPError(code, msg) if code not in (200, 500): raise HTTPError(code, msg) #return response payload return data.decode('utf-8')
def call(self, addr, data, namespace, soapaction=None, encoding=None): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @param data: data to be sent @param soapaction: soap action to be called @param encoding: encoding for the message @type addr: string @type data: string @type soapaction: string @type encoding: string @return: response payload @rtype: string """ # Build a request addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.query != '': real_path += '?%s' % addr.query if addr.scheme == 'https': r = httplib.HTTPS(real_addr) else: r = httplib.HTTP(real_addr) r.putrequest("POST", real_path) r.putheader("Host", addr.hostname) r.putheader("User-agent", 'BRISA SERVER') t = 'text/xml' if encoding: t += '; charset="%s"' % encoding r.putheader("Content-type", t) r.putheader("Content-length", str(len(data))) # if user is not a user:passwd format if addr.username != None: val = base64.encodestring(addr.user) r.putheader('Authorization', 'Basic ' + val.replace('\012', '')) # This fixes sending either "" or "None" if soapaction: r.putheader("SOAPAction", '"%s"' % soapaction) else: r.putheader("SOAPAction", "") r.endheaders() r.send(data) #read response line code, msg, headers = r.getreply() content_type = headers.get("content-type", "text/xml") content_length = headers.get("Content-length") if content_length == None: data = r.getfile().read() message_len = len(data) else: message_len = int(content_length) data = r.getfile().read(message_len) def startswith(string, val): return string[0:len(val)] == val if code == 500 and not \ (startswith(content_type, "text/xml") and message_len > 0): raise HTTPError(code, msg) if code not in (200, 500): raise HTTPError(code, msg) #return response payload return data.decode('utf-8')
def call(self, addr, data, namespace, soapaction=None, encoding=None): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @param data: data to be sent @param soapaction: soap action to be called @param encoding: encoding for the message @type addr: string @type data: string @type soapaction: string @type encoding: string @return: response payload @rtype: string """ log.debug('#### HTTPTransport call - addr : %s' % str(addr)) log.debug('#### HTTPTransport call - data : %s' % str(data)) log.debug('#### HTTPTransport call - namespace : %s' % str(namespace)) log.debug('#### HTTPTransport call - soapaction : %s' % str(soapaction)) log.debug('#### HTTPTransport call - encoding : %s' % str(encoding)) # Build a request addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.query: # Windows based media servers tend to use the query element of the address to hold connection information # TODO: standardise this code and only do the conversions for MS media player real_path += '?' + addr.query if data and soapaction.endswith('IsAuthorized'): # HACK - MS MP (check whether this is really the case) data = data.replace('<DeviceID />', '<DeviceID>""</DeviceID>') print "data after: " + data if data and soapaction.endswith('Browse'): # HACK - MS MP doesn't seem to like ObjectID element not being first in list # Actually seems to like a specific order (e.g. if req count is before starting index it doesn't bring anything back - # I guess because the index is after the end of the number it found) before = re.search('.*<ns[0-9]?:Browse[^>]*>', data) after = re.search('</ns[0-9]?:Browse>.*', data) objectid = re.search('<ObjectID>.*</ObjectID>', data) browseflag = re.search('<BrowseFlag>.*</BrowseFlag>', data) filter = re.search('<Filter>.*</Filter>', data) startingindex = re.search('<StartingIndex>.*</StartingIndex>', data) requestedcount = re.search('<RequestedCount>.*</RequestedCount>', data) sortcriteria = re.search('<SortCriteria>.*</SortCriteria>', data) if sortcriteria is None: sortcriteria = '<SortCriteria />' else: sortcriteria = sortcriteria.group() # print "data before: " + data data = before.group() + objectid.group() + browseflag.group() + filter.group() + startingindex.group() + requestedcount.group() + sortcriteria + after.group() # print "data after: " + data if data and soapaction.endswith('Search'): # HACK - MS MP doesn't seem to like elements not being in schema order before = re.search('.*<ns[0-9]?:Search[^>]*>', data) after = re.search('</ns[0-9]?:Search>.*', data) containerid = re.search('<ContainerID>.*</ContainerID>', data) searchcriteria = re.search('<SearchCriteria>.*</SearchCriteria>', data) filter = re.search('<Filter>.*</Filter>', data) startingindex = re.search('<StartingIndex>.*</StartingIndex>', data) requestedcount = re.search('<RequestedCount>.*</RequestedCount>', data) sortcriteria = re.search('<SortCriteria>.*</SortCriteria>', data) # print "data before: " + data data = before.group() + containerid.group() + searchcriteria.group() + filter.group() + startingindex.group() + requestedcount.group() + sortcriteria.group() + after.group() # print "data after: " + data if addr.scheme == 'https': r = httplib.HTTPSConnection(real_addr) else: r = httplib.HTTPConnection(real_addr) log.debug('#### HTTPTransport call - real_addr : %s' % real_addr) log.debug('#### HTTPTransport call - real_path : %s' % real_path) log.debug('#### HTTPTransport call - addr.scheme : %s' % addr.scheme) log.debug('#### HTTPTransport call - addr.hostname : %s' % addr.hostname) r.putrequest("POST", real_path, skip_host=1, skip_accept_encoding=1) # r.putheader("ACCEPT-ENCODING", 'gzip') r.putheader("CONNECTION", 'close') r.putheader("Host", addr.hostname) r.putheader("User-agent", 'BRISA SERVER') t = 'text/xml' if encoding: t += '; charset="%s"' % encoding r.putheader("Content-type", t) r.putheader("Content-length", str(len(data))) # if user is not a user:passwd format if addr.username != None: val = base64.encodestring(addr.user) r.putheader('Authorization', 'Basic ' + val.replace('\012', '')) # This fixes sending either "" or "None" if soapaction: r.putheader("SOAPAction", '"%s"' % soapaction) else: r.putheader("SOAPAction", "") r.endheaders() log.debug('#### HTTP BEFORE r.send ################################') r.send(data) log.debug('#### HTTP AFTER r.send ################################') #read response line # code, msg, headers = r.getreply() response = r.getresponse() code = response.status msg = response.reason headers = response.msg log.debug('#### HTTP AFTER START #################################') log.debug('#### HTTP code : %s' % str(code)) log.debug('#### HTTP msg : %s' % str(msg)) log.debug('#### HTTP headers : %s' % str(headers)) log.debug('#### HTTP AFTER END ###################################') content_type = headers.get("content-type", "text/xml") content_length = headers.get("Content-length") if content_length == None: # data = r.getfile().read() data = response.read() message_len = len(data) else: message_len = int(content_length) # data = r.getfile().read(message_len) data = response.read(message_len) def startswith(string, val): return string[0:len(val)] == val if code == 500 and not \ (startswith(content_type, "text/xml") and message_len > 0): raise HTTPError(code, msg) if code not in (200, 500): raise HTTPError(code, msg) # TODO: use the content-type charset to convert the data returned #return response payload # NAS is sending some non utf-8 data - TODO: fix NAS rather than decoding for all types which is redundant try: d = data.decode('utf-8', 'replace') except UnicodeDecodeError: print "UnicodeDecodeError" return data log.debug('#### HTTP data : %s' % d) return d
def call(self, addr, data, namespace, soapaction=None, encoding=None): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @param data: data to be sent @param soapaction: soap action to be called @param encoding: encoding for the message @type addr: string @type data: string @type soapaction: string @type encoding: string @return: response payload @rtype: string """ log.debug('#### HTTPTransport call - addr : %s' % str(addr)) log.debug('#### HTTPTransport call - data : %s' % str(data)) log.debug('#### HTTPTransport call - namespace : %s' % str(namespace)) log.debug('#### HTTPTransport call - soapaction : %s' % str(soapaction)) log.debug('#### HTTPTransport call - encoding : %s' % str(encoding)) # Build a request addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.query: # Windows based media servers tend to use the query element of the address to hold connection information # TODO: standardise this code and only do the conversions for MS media player real_path += '?' + addr.query if data and soapaction.endswith('IsAuthorized'): # HACK - MS MP (check whether this is really the case) data = data.replace('<DeviceID />', '<DeviceID>""</DeviceID>') print "data after: " + data if data and soapaction.endswith('Browse'): # HACK - MS MP doesn't seem to like ObjectID element not being first in list # Actually seems to like a specific order (e.g. if req count is before starting index it doesn't bring anything back - # I guess because the index is after the end of the number it found) before = re.search('.*<ns[0-9]?:Browse[^>]*>', data) after = re.search('</ns[0-9]?:Browse>.*', data) objectid = re.search('<ObjectID>.*</ObjectID>', data) browseflag = re.search('<BrowseFlag>.*</BrowseFlag>', data) filter = re.search('<Filter>.*</Filter>', data) startingindex = re.search('<StartingIndex>.*</StartingIndex>', data) requestedcount = re.search('<RequestedCount>.*</RequestedCount>', data) sortcriteria = re.search('<SortCriteria>.*</SortCriteria>', data) if sortcriteria is None: sortcriteria = '<SortCriteria />' else: sortcriteria = sortcriteria.group() # print "data before: " + data data = before.group() + objectid.group() + browseflag.group() + filter.group() + startingindex.group() + requestedcount.group() + sortcriteria + after.group() # print "data after: " + data if data and soapaction.endswith('Search'): # HACK - MS MP doesn't seem to like elements not being in schema order before = re.search('.*<ns[0-9]?:Search[^>]*>', data) after = re.search('</ns[0-9]?:Search>.*', data) containerid = re.search('<ContainerID>.*</ContainerID>', data) searchcriteria = re.search('<SearchCriteria>.*</SearchCriteria>', data) filter = re.search('<Filter>.*</Filter>', data) startingindex = re.search('<StartingIndex>.*</StartingIndex>', data) requestedcount = re.search('<RequestedCount>.*</RequestedCount>', data) sortcriteria = re.search('<SortCriteria>.*</SortCriteria>', data) # print "data before: " + data data = before.group() + containerid.group() + searchcriteria.group() + filter.group() + startingindex.group() + requestedcount.group() + sortcriteria.group() + after.group() # print "data after: " + data if addr.scheme == 'https': r = httplib.HTTPSConnection(real_addr) else: r = httplib.HTTPConnection(real_addr) log.debug('#### HTTPTransport call - real_addr : %s' % real_addr) log.debug('#### HTTPTransport call - real_path : %s' % real_path) log.debug('#### HTTPTransport call - addr.scheme : %s' % addr.scheme) log.debug('#### HTTPTransport call - addr.hostname : %s' % addr.hostname) r.putrequest("POST", real_path, skip_host=1, skip_accept_encoding=1) # r.putheader("ACCEPT-ENCODING", 'gzip') r.putheader("CONNECTION", 'close') r.putheader("Host", addr.hostname) # r.putheader("User-agent", 'BRISA SERVER') r.putheader("User-agent", 'Sonospy') t = 'text/xml' if encoding: t += '; charset="%s"' % encoding r.putheader("Content-type", t) r.putheader("Content-length", str(len(data))) # if user is not a user:passwd format if addr.username != None: val = base64.encodestring(addr.user) r.putheader('Authorization', 'Basic ' + val.replace('\012', '')) # This fixes sending either "" or "None" if soapaction: r.putheader("SOAPAction", '"%s"' % soapaction) else: r.putheader("SOAPAction", "") r.endheaders() log.debug('#### HTTP BEFORE r.send ################################') r.send(data) log.debug('#### HTTP AFTER r.send ################################') #read response line # code, msg, headers = r.getreply() response = r.getresponse() code = response.status msg = response.reason headers = response.msg log.debug('#### HTTP AFTER START #################################') log.debug('#### HTTP code : %s' % str(code)) log.debug('#### HTTP msg : %s' % str(msg)) log.debug('#### HTTP headers : %s' % str(headers)) log.debug('#### HTTP AFTER END ###################################') content_type = headers.get("content-type", "text/xml") content_length = headers.get("Content-length") if content_length == None: # data = r.getfile().read() data = response.read() message_len = len(data) else: message_len = int(content_length) # data = r.getfile().read(message_len) data = response.read(message_len) def startswith(string, val): return string[0:len(val)] == val if code == 500 and not \ (startswith(content_type, "text/xml") and message_len > 0): raise HTTPError(code, msg) if code not in (200, 500): raise HTTPError(code, msg) # TODO: use the content-type charset to convert the data returned #return response payload # NAS is sending some non utf-8 data - TODO: fix NAS rather than decoding for all types which is redundant try: d = data.decode('utf-8', 'replace') except UnicodeDecodeError: print "UnicodeDecodeError" return data log.debug('#### HTTP data : %s' % d) return d
def call(self, addr, data, namespace, soapaction=None, encoding=None): """ Builds and performs an HTTP request. Returns the response payload. @param addr: address to receive the request in the form schema://hostname:port @param data: data to be sent @param soapaction: soap action to be called @param encoding: encoding for the message @type addr: string @type data: string @type soapaction: string @type encoding: string @return: response payload @rtype: string """ log.debug('#### HTTPTransport call - addr : %s' % str(addr)) log.debug('#### HTTPTransport call - data : %s' % str(data)) log.debug('#### HTTPTransport call - namespace : %s' % str(namespace)) log.debug('#### HTTPTransport call - soapaction : %s' % str(soapaction)) log.debug('#### HTTPTransport call - encoding : %s' % str(encoding)) # Build a request ''' addr : http://legato.radiotime.com:80 data : <?xml version="1.0" encoding="utf-8"?><s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><credentials xmlns="http://www.sonos.com/Services/1.1"><deviceProvider>Sonos</deviceProvider></credentials></s:Header><s:Body><ns0:getMetadata xmlns:ns0="http://www.sonos.com/Services/1.1"><count>100</count><index>0</index><recursive>false</recursive><id>root</id></ns0:getMetadata></s:Body></s:Envelope> namespace : ('u', 'http://www.sonos.com/Services/1.1') soapaction : http://www.sonos.com/Services/1.1#getMetadata encoding : utf-8 real_addr : legato.radiotime.com:80 real_path : addr.scheme : http addr.hostname : legato.radiotime.com POST /Radio.asmx HTTP/1.1 CONNECTION: close ACCEPT-ENCODING: gzip HOST: legato.radiotime.com USER-AGENT: Linux UPnP/1.0 Sonos/11.7-19141a CONTENT-LENGTH: 337 CONTENT-TYPE: text/xml; charset="utf-8" ACCEPT-LANGUAGE: en-US SOAPACTION: "http://www.sonos.com/Services/1.1#getMetadata" ''' # TODO: tidy up parameters, use saved params from musicservices call, change to gzip addr = parse_url(addr) real_addr = '%s:%d' % (addr.hostname, addr.port) real_path = addr.path if addr.scheme == 'https': r = httplib.HTTPSConnection(real_addr) else: r = httplib.HTTPConnection(real_addr) log.debug('#### HTTPTransport call - real_addr : %s' % real_addr) log.debug('#### HTTPTransport call - real_path : %s' % real_path) log.debug('#### HTTPTransport call - addr.scheme : %s' % addr.scheme) log.debug('#### HTTPTransport call - addr.hostname : %s' % addr.hostname) r.putrequest("POST", real_path, skip_host=1, skip_accept_encoding=1) r.putheader("ACCEPT-ENCODING", 'gzip') r.putheader("CONNECTION", 'close') r.putheader("HOST", addr.hostname) r.putheader("USER-AGENT", 'Linux UPnP/1.0 Sonos/11.7-19141a') t = 'text/xml' # if encoding: # t += '; charset="%s"' % encoding r.putheader("CONTENT-TYPE", t) # r.putheader("ACCEPT-CHARSET", 'ISO-8859-1,utf-8;q=0.7,*;q=0.7') r.putheader("ACCEPT-LANGUAGE", 'en-US') r.putheader("CONTENT-LENGTH", str(len(data))) # if user is not a user:passwd format if addr.username != None: val = base64.encodestring(addr.user) r.putheader('Authorization', 'Basic ' + val.replace('\012', '')) # This fixes sending either "" or "None" if soapaction: r.putheader("SOAPACTION", '"%s"' % soapaction) else: r.putheader("SOAPACTION", "") r.endheaders() log.debug('#### HTTP BEFORE r.send ################################') r.send(data) log.debug('#### HTTP AFTER r.send ################################') #read response line # code, msg, headers = r.getreply() response = r.getresponse() code = response.status msg = response.reason headers = response.msg log.debug('#### HTTP AFTER START #################################') log.debug('#### HTTP code : %s' % str(code)) log.debug('#### HTTP msg : %s' % str(msg)) log.debug('#### HTTP headers : %s' % str(headers)) log.debug('#### HTTP AFTER END ###################################') content_type = headers.get("content-type", "text/xml") content_length = headers.get("Content-length") if content_length == None: # data = r.getfile().read() data = response.read() message_len = len(data) else: message_len = int(content_length) # data = r.getfile().read(message_len) data = response.read(message_len) def startswith(string, val): return string[0:len(val)] == val if code == 500 and not \ (startswith(content_type, "text/xml") and message_len > 0): raise HTTPError(code, msg) if code not in (200, 500): raise HTTPError(code, msg) #return response payload # return data.decode('utf-8') import StringIO stream = StringIO.StringIO(data) import gzip gzipper = gzip.GzipFile(fileobj=stream) data = gzipper.read() return data