1 # Written by Ingar Arntzen
2 # see LICENSE.txt for license information
5 This module implements parsing and creation of soap messages
6 specified by the UPnP specification, action requests, action responses,
7 error responses and event messages.
9 import xml.dom.minidom as minidom
12 401: "Invalid Action",
15 600: "Argument Value Invalid",
16 601: "Argument Value Out of Range",
17 602: "Optional Action Not Implemented",
19 604: "Human Intervention Required",
20 605: "String Argument Too Long",
21 606: "Action Not Authorized",
22 607: "Signature Failure",
23 608: "Signature Missing",
25 610: "Invalid Sequence",
26 611: "Invalid Control URL",
27 612: "No Such Session",
31 def _get_element_data(element):
32 """Get all data contained in element."""
35 for node in element.childNodes:
36 if node.nodeType == node.TEXT_NODE:
37 data += str(node.data)
38 elif node.nodeType == node.ELEMENT_NODE:
39 data += str(node.toxml())
40 elif node.nodeType == node.CDATA_SECTION_NODE:
41 data += str(node.data)
44 ##############################################
45 # PARSE ACTION REQUEST
46 ##############################################
50 """This implements parsing of action requests made by
51 UPnP control points. The soap message corresponds to
52 the body of the HTTP POST request."""
56 self._action_name = ""
60 def parse(self, xmldata):
61 """This parses the xmldata and makes the included information
64 doc = minidom.parseString(xmldata.replace('\n', ''))
65 envelope = doc.documentElement
66 body = envelope.firstChild
67 action_element = body.firstChild
69 for arg_element in action_element.childNodes:
70 data = _get_element_data(arg_element)
71 args.append((str(arg_element.tagName), data))
72 except (TypeError, AttributeError):
76 self._action_name = action_element.localName
77 self._ns = action_element.namespaceURI
81 def get_action_name(self):
82 """Retrieve the name of the requested UPnP action."""
84 return self._action_name
86 def get_namespace(self):
87 """Retrieve the namespace of the requested UPnP action,
88 i.e. the service type it refers to."""
92 def get_arguments(self):
93 """Retrieve the in-arguments associated with
94 the UPnP action request."""
99 """Reset so that a new soap message may be parsed by the
104 self._action_name = ""
109 _INSTANCE = _ActionRequest()
111 def parse_action_request(data):
112 """This function parses the soap xml request and
113 returns three values. The function hides the details of
116 action -- name of action
117 ns -- namespace (i.e. service type)
118 args -- action in-arguments.
120 success = _INSTANCE.parse(data)
124 action_name = _INSTANCE.get_action_name()
125 name_space = _INSTANCE.get_namespace()
126 args = _INSTANCE.get_arguments()
128 return action_name, name_space, args
131 ##############################################
132 # CREATE ACTION REQUEST
133 ##############################################
135 ACTION_REQUEST_FMT = """<?xml version="1.0"?>
137 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding"
138 xmlns:s="http://schemas.xmlsoap.org/soap/envelope">
147 ACTION_REQUEST_FMT = ACTION_REQUEST_FMT.replace('\n', '')
149 ARG_FMT = "<%s>%s</%s>"
151 def create_action_request(service_type, action_name, args):
152 """Create action request. Returns string."""
154 for arg_name, arg_value in args:
155 data_str += ARG_FMT % (arg_name, arg_value, arg_name)
156 return ACTION_REQUEST_FMT % (action_name, service_type,
157 data_str, action_name)
160 ##############################################
161 # CREATE ACTION RESPONSE
162 ##############################################
164 ACTION_RESPONSE_FMT = u"""<?xml version="1.0"?>
166 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding"
167 xmlns:s="http://schemas.xmlsoap.org/soap/envelope">
176 ACTION_RESPONSE_FMT = ACTION_RESPONSE_FMT.replace('\n', '')
178 RESULT_FMT = u"<%s>%s</%s>"
180 def create_action_response(name_space, action_name, result_list):
181 """This function creates a soap xml action response,
182 given three parameters.
184 name_space -- namespace (i.e. upnp service type)
185 action_name -- name of upnp action
186 result_list -- list of result values, i.e. (argName, argValue) - tuples
188 name_space = unicode(name_space)
189 action_name = unicode(action_name)
191 for (result_name, result_value) in result_list:
192 result_name = unicode(result_name)
193 result_value = unicode(result_value)
194 data_str += RESULT_FMT % (result_name, result_value, result_name)
195 return ACTION_RESPONSE_FMT % (action_name, name_space, data_str, action_name)
198 ##############################################
199 # PARSE ACTION RESPONSE
200 ##############################################
201 def parse_action_response(xmldata):
202 """This parses the xmldata and makes the included information
203 easily accessible."""
205 doc = minidom.parseString(xmldata.replace('\n', ''))
208 envelope_elem = doc.documentElement
209 body_elem = envelope_elem.firstChild
210 action_elem = body_elem.firstChild
212 for arg_elem in action_elem.childNodes:
213 data = _get_element_data(arg_elem)
214 args.append((str(arg_elem.tagName), data))
215 except (TypeError, AttributeError):
220 result['action_name'] = str(action_elem.localName[:-8])
221 result['service_type'] = str(action_elem.namespaceURI)
222 result['arguments'] = args
226 ##############################################
228 ##############################################
231 ERROR_RESPONSE_FMT = u"""<?xml version="1.0" ?>
233 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding"
234 xmlns:s="http://schemas.xmlsoap.org/soap/envelope">
237 <faultcode>s:Client</faultcode>
238 <faultstring>UPnPError</faultstring>
240 <UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
241 <errorCode>%s</errorCode>
242 <errorDescription>%s</errorDescription>
249 ERROR_RESPONSE_FMT = ERROR_RESPONSE_FMT.replace('\n', '')
252 def create_error_response(error_code, error_description):
253 """This function creates a soap xml UPnP error response."""
254 error_code = unicode(error_code)
255 error_description = unicode(error_description)
256 return ERROR_RESPONSE_FMT % (error_code, error_description)
259 ##############################################
260 # CREATE EVENT MESSAGE
261 ##############################################
263 _EVENT_MSG_XML_FMT = u"""<?xml version="1.0"?>
264 <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
265 %s</e:propertyset>"""
266 _EVENT_MSG_PROP_FMT = u"<e:property>\n<%s>%s</%s>\n</e:property>\n"
268 def create_event_message(variables):
269 """This function creates a soap xml UPnP event message.
271 variables -- list of recently update state variables (name, data) tuples.
274 for name, data in variables:
277 data_str += _EVENT_MSG_PROP_FMT % (name, data, name)
278 return _EVENT_MSG_XML_FMT % data_str
281 ##############################################
282 # PARSE EVENT MESSAGE
283 ##############################################
285 def parse_event_message(xmldata):
286 """This parses the xmldata and makes the included information
287 easily accessible."""
288 doc = minidom.parseString(xmldata.replace('\n', ''))
291 property_set_elem = doc.documentElement
293 for property_elem in property_set_elem.childNodes:
294 var_elem = property_elem.firstChild
295 data = _get_element_data(var_elem)
296 tuples.append((str(var_elem.tagName), data))
300 ##############################################
302 ##############################################
304 if __name__ == '__main__':
306 REQUEST_XML = """<?xml version="1.0"?>
308 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
309 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
311 <ns0:SetTarget xmlns:ns0="urn:schemas-upnp-org:service:SwitchPower:1">
312 <newTargetValue>True</newTargetValue>
317 REQUEST_XML = REQUEST_XML.replace('\n', '')
318 print parse_action_request(REQUEST_XML)
319 print parse_action_request(REQUEST_XML)
322 SERVICE_TYPE = "urn:schemas-upnp-org:service:ServiceType:1"
323 ACTION_NAME = "GetTarget"
324 RESULT_LIST = [('result1', 'True'), ('result2', '4')]
325 RESPONSE_XML = create_action_response(SERVICE_TYPE,
326 ACTION_NAME, RESULT_LIST)
328 print create_error_response("501", "Action not implemented")
330 VARIABLES = [('var1', 'jalla'), ('var2', 'palla')]
331 EVENT_MSG = create_event_message(VARIABLES)
333 print parse_event_message(EVENT_MSG)
335 print create_action_request(SERVICE_TYPE,
336 ACTION_NAME, VARIABLES)
338 print parse_action_response(RESPONSE_XML)