instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / UPnP / common / upnpsoap.py
1 # Written by Ingar Arntzen
2 # see LICENSE.txt for license information
3
4 """
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.
8 """
9 import xml.dom.minidom as minidom
10
11 ERROR_CODES = {
12     401: "Invalid Action",
13     402: "Invalid Args",
14     501: "Action Failed",
15     600: "Argument Value Invalid",
16     601: "Argument Value Out of Range",
17     602: "Optional Action Not Implemented",
18     603: "Out of Memory",
19     604: "Human Intervention Required",
20     605: "String Argument Too Long",
21     606: "Action Not Authorized",
22     607: "Signature Failure",
23     608: "Signature Missing",
24     609: "Not Encrypted",
25     610: "Invalid Sequence",
26     611: "Invalid Control URL",
27     612: "No Such Session",
28 }
29
30
31 def _get_element_data(element):
32     """Get all data contained in element."""
33     data = ""
34     if element != None:
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)
42     return data
43
44 ##############################################
45 # PARSE ACTION REQUEST
46 ##############################################
47
48 class _ActionRequest:
49
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."""
53
54     def __init__(self):
55         self._doc = None
56         self._action_name = ""
57         self._ns = ""
58         self._args = []
59
60     def parse(self, xmldata):
61         """This parses the xmldata and makes the included information
62         easily accessible."""
63         try:
64             doc = minidom.parseString(xmldata.replace('\n', ''))
65             envelope = doc.documentElement
66             body = envelope.firstChild
67             action_element = body.firstChild
68             args = []
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):
73             return False
74
75         self._doc = doc
76         self._action_name = action_element.localName
77         self._ns = action_element.namespaceURI
78         self._args = args
79         return True
80         
81     def get_action_name(self):
82         """Retrieve the name of the requested UPnP action."""
83         if self._doc: 
84             return self._action_name
85
86     def get_namespace(self):
87         """Retrieve the namespace of the requested UPnP action,
88         i.e. the service type it refers to."""
89         if self._doc: 
90             return self._ns
91
92     def get_arguments(self):
93         """Retrieve the in-arguments associated with 
94         the UPnP action request."""
95         if self._doc: 
96             return self._args
97
98     def reset(self):
99         """Reset so that a new soap message may be parsed by the
100         same instance."""
101         if self._doc:
102             self._doc.unlink()
103             self._doc = None
104         self._action_name = ""
105         self._ns = ""
106         self._args = []
107
108
109 _INSTANCE = _ActionRequest()
110
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 
114     implementation.
115
116     action -- name of action
117     ns -- namespace (i.e. service type)
118     args -- action in-arguments.
119     """
120     success = _INSTANCE.parse(data)
121     if not success: 
122         return None
123     else:
124         action_name = _INSTANCE.get_action_name()
125         name_space = _INSTANCE.get_namespace()
126         args = _INSTANCE.get_arguments()
127         _INSTANCE.reset()
128         return action_name, name_space, args
129
130
131 ##############################################
132 # CREATE ACTION REQUEST
133 ##############################################
134
135 ACTION_REQUEST_FMT = """<?xml version="1.0"?>
136 <s:Envelope 
137 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding" 
138 xmlns:s="http://schemas.xmlsoap.org/soap/envelope">
139 <s:Body>
140 <u:%s 
141 xmlns:u="%s">
142 %s
143 </u:%s>
144 </s:Body>
145 </s:Envelope>
146 """
147 ACTION_REQUEST_FMT = ACTION_REQUEST_FMT.replace('\n', '')
148
149 ARG_FMT = "<%s>%s</%s>"
150
151 def create_action_request(service_type, action_name, args):
152     """Create action request. Returns string."""
153     data_str = ""
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)
158
159
160 ##############################################
161 # CREATE ACTION RESPONSE
162 ##############################################
163
164 ACTION_RESPONSE_FMT = u"""<?xml version="1.0"?>
165 <s:Envelope 
166 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding" 
167 xmlns:s="http://schemas.xmlsoap.org/soap/envelope">
168 <s:Body>
169 <u:%sResponse 
170 xmlns:u="%s">
171 %s
172 </u:%sResponse>
173 </s:Body>
174 </s:Envelope>"""
175
176 ACTION_RESPONSE_FMT = ACTION_RESPONSE_FMT.replace('\n', '')
177
178 RESULT_FMT = u"<%s>%s</%s>"
179
180 def create_action_response(name_space, action_name, result_list):
181     """This function creates a soap xml action response,
182     given three parameters.
183
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
187     """
188     name_space = unicode(name_space)
189     action_name = unicode(action_name)
190     data_str = ""
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)
196
197
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."""
204     try:
205         doc = minidom.parseString(xmldata.replace('\n', ''))
206         if doc == None: 
207             return None    
208         envelope_elem = doc.documentElement
209         body_elem = envelope_elem.firstChild
210         action_elem = body_elem.firstChild
211         args = []
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):
216         return None
217
218     result = {}
219     # actionNameResponse
220     result['action_name'] = str(action_elem.localName[:-8])
221     result['service_type'] = str(action_elem.namespaceURI)
222     result['arguments'] = args
223     return result
224
225
226 ##############################################
227 # ERROR RESPONSE
228 ##############################################
229
230
231 ERROR_RESPONSE_FMT = u"""<?xml version="1.0" ?>
232 <s:Envelope 
233 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding" 
234 xmlns:s="http://schemas.xmlsoap.org/soap/envelope">
235 <s:Body>
236 <s:Fault>
237 <faultcode>s:Client</faultcode>
238 <faultstring>UPnPError</faultstring>
239 <detail>
240 <UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
241 <errorCode>%s</errorCode>
242 <errorDescription>%s</errorDescription>
243 </UPnPError>
244 </detail>
245 </s:Fault>
246 </s:Body>
247 </s:Envelope>
248 """
249 ERROR_RESPONSE_FMT = ERROR_RESPONSE_FMT.replace('\n', '')
250
251
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)
257
258
259 ##############################################
260 # CREATE EVENT MESSAGE
261 ##############################################
262
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"
267
268 def create_event_message(variables):
269     """This function creates a soap xml UPnP event message.
270
271     variables -- list of recently update state variables (name, data) tuples.
272     """
273     data_str = ""
274     for name, data in variables:
275         name = unicode(name)
276         data = unicode(data)
277         data_str += _EVENT_MSG_PROP_FMT % (name, data, name)
278     return _EVENT_MSG_XML_FMT % data_str
279
280
281 ##############################################
282 # PARSE EVENT MESSAGE
283 ##############################################
284
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', ''))
289     if doc == None: 
290         return None    
291     property_set_elem = doc.documentElement
292     tuples = []
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))
297     return tuples
298
299         
300 ##############################################
301 # MAIN
302 ##############################################
303
304 if __name__ == '__main__':
305
306     REQUEST_XML = """<?xml version="1.0"?>
307 <s:Envelope 
308 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
309 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
310 <s:Body>
311 <ns0:SetTarget xmlns:ns0="urn:schemas-upnp-org:service:SwitchPower:1">
312 <newTargetValue>True</newTargetValue>
313 </ns0:SetTarget>
314 </s:Body>
315 </s:Envelope>"""
316
317     REQUEST_XML = REQUEST_XML.replace('\n', '')
318     print parse_action_request(REQUEST_XML)
319     print parse_action_request(REQUEST_XML)
320
321
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)
327     print RESPONSE_XML
328     print create_error_response("501", "Action not implemented")
329
330     VARIABLES = [('var1', 'jalla'), ('var2', 'palla')]
331     EVENT_MSG = create_event_message(VARIABLES)
332     print EVENT_MSG
333     print parse_event_message(EVENT_MSG)
334
335     print create_action_request(SERVICE_TYPE, 
336                                 ACTION_NAME, VARIABLES)
337
338     print parse_action_response(RESPONSE_XML)