A Python UPnP Media Server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

621 lines
21 KiB

  1. """
  2. ################################################################################
  3. # Copyright (c) 2003, Pfizer
  4. # Copyright (c) 2001, Cayce Ullman.
  5. # Copyright (c) 2001, Brian Matthews.
  6. #
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions are met:
  11. # Redistributions of source code must retain the above copyright notice, this
  12. # list of conditions and the following disclaimer.
  13. #
  14. # Redistributions in binary form must reproduce the above copyright notice,
  15. # this list of conditions and the following disclaimer in the documentation
  16. # and/or other materials provided with the distribution.
  17. #
  18. # Neither the name of actzero, inc. nor the names of its contributors may
  19. # be used to endorse or promote products derived from this software without
  20. # specific prior written permission.
  21. #
  22. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  23. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25. # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
  26. # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  27. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  28. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  29. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  30. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  31. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. #
  33. ################################################################################
  34. """
  35. ident = '$Id: SOAPBuilder.py,v 1.19 2004/04/10 04:28:46 irjudson Exp $'
  36. from version import __version__
  37. import cgi
  38. import copy
  39. from wstools.XMLname import toXMLname, fromXMLname
  40. import fpconst
  41. # SOAPpy modules
  42. from Config import Config
  43. from NS import NS
  44. from Types import *
  45. # Test whether this Python version has Types.BooleanType
  46. # If it doesn't have it, then False and True are serialized as integers
  47. try:
  48. BooleanType
  49. pythonHasBooleanType = 1
  50. except NameError:
  51. pythonHasBooleanType = 0
  52. ################################################################################
  53. # SOAP Builder
  54. ################################################################################
  55. class SOAPBuilder:
  56. _xml_top = '<?xml version="1.0"?>\n'
  57. _xml_enc_top = '<?xml version="1.0" encoding="%s"?>\n'
  58. _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \
  59. NS.__dict__
  60. _env_bot = '</%(ENV_T)s:Envelope>\n' % NS.__dict__
  61. # Namespaces potentially defined in the Envelope tag.
  62. _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T,
  63. NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T,
  64. NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T}
  65. def __init__(self, args = (), kw = {}, method = None, namespace = None,
  66. header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8',
  67. use_refs = 0, config = Config, noroot = 0):
  68. # Test the encoding, raising an exception if it's not known
  69. if encoding != None:
  70. ''.encode(encoding)
  71. self.args = args
  72. self.kw = kw
  73. self.envelope = envelope
  74. self.encoding = encoding
  75. self.method = method
  76. self.namespace = namespace
  77. self.header = header
  78. self.methodattrs= methodattrs
  79. self.use_refs = use_refs
  80. self.config = config
  81. self.out = []
  82. self.tcounter = 0
  83. self.ncounter = 1
  84. self.icounter = 1
  85. self.envns = {}
  86. self.ids = {}
  87. self.depth = 0
  88. self.multirefs = []
  89. self.multis = 0
  90. self.body = not isinstance(args, bodyType)
  91. self.noroot = noroot
  92. def build(self):
  93. if Config.debug: print "In build."
  94. ns_map = {}
  95. # Cache whether typing is on or not
  96. typed = self.config.typed
  97. if self.header:
  98. # Create a header.
  99. self.dump(self.header, "Header", typed = typed)
  100. self.header = None # Wipe it out so no one is using it.
  101. if self.body:
  102. # Call genns to record that we've used SOAP-ENV.
  103. self.depth += 1
  104. body_ns = self.genns(ns_map, NS.ENV)[0]
  105. self.out.append("<%sBody>\n" % body_ns)
  106. if self.method:
  107. self.depth += 1
  108. a = ''
  109. if self.methodattrs:
  110. for (k, v) in self.methodattrs.items():
  111. a += ' %s="%s"' % (k, v)
  112. if self.namespace: # Use the namespace info handed to us
  113. methodns, n = self.genns(ns_map, self.namespace)
  114. else:
  115. methodns, n = '', ''
  116. self.out.append('<%s%s%s%s%s>\n' % (
  117. methodns, self.method, n, a, self.genroot(ns_map)))
  118. try:
  119. if type(self.args) != TupleType:
  120. args = (self.args,)
  121. else:
  122. args = self.args
  123. for i in args:
  124. self.dump(i, typed = typed, ns_map = ns_map)
  125. if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method):
  126. for k in self.config.argsOrdering.get(self.method):
  127. self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map)
  128. else:
  129. for (k, v) in self.kw.items():
  130. self.dump(v, k, typed = typed, ns_map = ns_map)
  131. except RecursionError:
  132. if self.use_refs == 0:
  133. # restart
  134. b = SOAPBuilder(args = self.args, kw = self.kw,
  135. method = self.method, namespace = self.namespace,
  136. header = self.header, methodattrs = self.methodattrs,
  137. envelope = self.envelope, encoding = self.encoding,
  138. use_refs = 1, config = self.config)
  139. return b.build()
  140. raise
  141. if self.method:
  142. self.out.append("</%s%s>\n" % (methodns, self.method))
  143. self.depth -= 1
  144. if self.body:
  145. # dump may add to self.multirefs, but the for loop will keep
  146. # going until it has used all of self.multirefs, even those
  147. # entries added while in the loop.
  148. self.multis = 1
  149. for obj, tag in self.multirefs:
  150. self.dump(obj, tag, typed = typed, ns_map = ns_map)
  151. self.out.append("</%sBody>\n" % body_ns)
  152. self.depth -= 1
  153. if self.envelope:
  154. e = map (lambda ns: ' xmlns:%s="%s"' % (ns[1], ns[0]),
  155. self.envns.items())
  156. self.out = ['<', self._env_top] + e + ['>\n'] + \
  157. self.out + \
  158. [self._env_bot]
  159. if self.encoding != None:
  160. self.out.insert(0, self._xml_enc_top % self.encoding)
  161. return ''.join(self.out).encode(self.encoding)
  162. self.out.insert(0, self._xml_top)
  163. return ''.join(self.out)
  164. def gentag(self):
  165. if Config.debug: print "In gentag."
  166. self.tcounter += 1
  167. return "v%d" % self.tcounter
  168. def genns(self, ns_map, nsURI):
  169. if nsURI == None:
  170. return ('', '')
  171. if type(nsURI) == TupleType: # already a tuple
  172. if len(nsURI) == 2:
  173. ns, nsURI = nsURI
  174. else:
  175. ns, nsURI = None, nsURI[0]
  176. else:
  177. ns = None
  178. if ns_map.has_key(nsURI):
  179. return (ns_map[nsURI] + ':', '')
  180. if self._env_ns.has_key(nsURI):
  181. ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI]
  182. return (ns + ':', '')
  183. if not ns:
  184. ns = "ns%d" % self.ncounter
  185. self.ncounter += 1
  186. ns_map[nsURI] = ns
  187. if self.config.buildWithNamespacePrefix:
  188. return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI))
  189. else:
  190. return ('', ' xmlns="%s"' % (nsURI))
  191. def genroot(self, ns_map):
  192. if self.noroot:
  193. return ''
  194. if self.depth != 2:
  195. return ''
  196. ns, n = self.genns(ns_map, NS.ENC)
  197. return ' %sroot="%d"%s' % (ns, not self.multis, n)
  198. # checkref checks an element to see if it needs to be encoded as a
  199. # multi-reference element or not. If it returns None, the element has
  200. # been handled and the caller can continue with subsequent elements.
  201. # If it returns a string, the string should be included in the opening
  202. # tag of the marshaled element.
  203. def checkref(self, obj, tag, ns_map):
  204. if self.depth < 2:
  205. return ''
  206. if not self.ids.has_key(id(obj)):
  207. n = self.ids[id(obj)] = self.icounter
  208. self.icounter = n + 1
  209. if self.use_refs == 0:
  210. return ''
  211. if self.depth == 2:
  212. return ' id="i%d"' % n
  213. self.multirefs.append((obj, tag))
  214. else:
  215. if self.use_refs == 0:
  216. raise RecursionError, "Cannot serialize recursive object"
  217. n = self.ids[id(obj)]
  218. if self.multis and self.depth == 2:
  219. return ' id="i%d"' % n
  220. self.out.append('<%s href="#i%d"%s/>\n' %
  221. (tag, n, self.genroot(ns_map)))
  222. return None
  223. # dumpers
  224. def dump(self, obj, tag = None, typed = 1, ns_map = {}):
  225. if Config.debug: print "In dump.", "obj=", obj
  226. ns_map = ns_map.copy()
  227. self.depth += 1
  228. if type(tag) not in (NoneType, StringType, UnicodeType):
  229. raise KeyError, "tag must be a string or None"
  230. try:
  231. meth = getattr(self, "dump_" + type(obj).__name__)
  232. except AttributeError:
  233. if type(obj) == LongType:
  234. obj_type = "integer"
  235. elif pythonHasBooleanType and type(obj) == BooleanType:
  236. obj_type = "boolean"
  237. else:
  238. obj_type = type(obj).__name__
  239. self.out.append(self.dumper(None, obj_type, obj, tag, typed,
  240. ns_map, self.genroot(ns_map)))
  241. else:
  242. meth(obj, tag, typed, ns_map)
  243. self.depth -= 1
  244. # generic dumper
  245. def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {},
  246. rootattr = '', id = '',
  247. xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s</%(tag)s>\n'):
  248. if Config.debug: print "In dumper."
  249. if nsURI == None:
  250. nsURI = self.config.typesNamespaceURI
  251. tag = tag or self.gentag()
  252. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  253. a = n = t = ''
  254. if typed and obj_type:
  255. ns, n = self.genns(ns_map, nsURI)
  256. ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
  257. t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n)
  258. try: a = obj._marshalAttrs(ns_map, self)
  259. except: pass
  260. try: data = obj._marshalData()
  261. except:
  262. if (obj_type != "string"): # strings are already encoded
  263. data = cgi.escape(str(obj))
  264. else:
  265. data = obj
  266. return xml % {"tag": tag, "type": t, "data": data, "root": rootattr,
  267. "id": id, "attrs": a}
  268. def dump_float(self, obj, tag, typed = 1, ns_map = {}):
  269. if Config.debug: print "In dump_float."
  270. tag = tag or self.gentag()
  271. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  272. if Config.strict_range:
  273. doubleType(obj)
  274. if fpconst.isPosInf(obj):
  275. obj = "INF"
  276. elif fpconst.isNegInf(obj):
  277. obj = "-INF"
  278. elif fpconst.isNaN(obj):
  279. obj = "NaN"
  280. else:
  281. obj = str(obj)
  282. # Note: python 'float' is actually a SOAP 'double'.
  283. self.out.append(self.dumper(None, "double", obj, tag, typed, ns_map,
  284. self.genroot(ns_map)))
  285. def dump_string(self, obj, tag, typed = 0, ns_map = {}):
  286. if Config.debug: print "In dump_string."
  287. tag = tag or self.gentag()
  288. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  289. id = self.checkref(obj, tag, ns_map)
  290. if id == None:
  291. return
  292. try: data = obj._marshalData()
  293. except: data = obj
  294. self.out.append(self.dumper(None, "string", cgi.escape(data), tag,
  295. typed, ns_map, self.genroot(ns_map), id))
  296. dump_str = dump_string # For Python 2.2+
  297. dump_unicode = dump_string
  298. def dump_None(self, obj, tag, typed = 0, ns_map = {}):
  299. if Config.debug: print "In dump_None."
  300. tag = tag or self.gentag()
  301. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  302. ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
  303. self.out.append('<%s %snull="1"%s/>\n' %
  304. (tag, ns, self.genroot(ns_map)))
  305. dump_NoneType = dump_None # For Python 2.2+
  306. def dump_list(self, obj, tag, typed = 1, ns_map = {}):
  307. if Config.debug: print "In dump_list.", "obj=", obj
  308. tag = tag or self.gentag()
  309. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  310. if type(obj) == InstanceType:
  311. data = obj.data
  312. else:
  313. data = obj
  314. id = self.checkref(obj, tag, ns_map)
  315. if id == None:
  316. return
  317. try:
  318. sample = data[0]
  319. empty = 0
  320. except:
  321. # preserve type if present
  322. if getattr(obj,"_typed",None) and getattr(obj,"_type",None):
  323. if getattr(obj, "_complexType", None):
  324. sample = typedArrayType(typed=obj._type,
  325. complexType = obj._complexType)
  326. sample._typename = obj._type
  327. obj._ns = NS.URN
  328. else:
  329. sample = typedArrayType(typed=obj._type)
  330. else:
  331. sample = structType()
  332. empty = 1
  333. # First scan list to see if all are the same type
  334. same_type = 1
  335. if not empty:
  336. for i in data[1:]:
  337. if type(sample) != type(i) or \
  338. (type(sample) == InstanceType and \
  339. sample.__class__ != i.__class__):
  340. same_type = 0
  341. break
  342. ndecl = ''
  343. if same_type:
  344. if (isinstance(sample, structType)) or \
  345. type(sample) == DictType or \
  346. (isinstance(sample, anyType) and \
  347. (getattr(sample, "_complexType", None) and \
  348. sample._complexType)): # force to urn struct
  349. try:
  350. tns = obj._ns or NS.URN
  351. except:
  352. tns = NS.URN
  353. ns, ndecl = self.genns(ns_map, tns)
  354. try:
  355. typename = sample._typename
  356. except:
  357. typename = "SOAPStruct"
  358. t = ns + typename
  359. elif isinstance(sample, anyType):
  360. ns = sample._validNamespaceURI(self.config.typesNamespaceURI,
  361. self.config.strictNamespaces)
  362. if ns:
  363. ns, ndecl = self.genns(ns_map, ns)
  364. t = ns + sample._type
  365. else:
  366. t = 'ur-type'
  367. else:
  368. typename = type(sample).__name__
  369. # For Python 2.2+
  370. if type(sample) == StringType: typename = 'string'
  371. # HACK: python 'float' is actually a SOAP 'double'.
  372. if typename=="float": typename="double"
  373. t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
  374. typename
  375. else:
  376. t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
  377. "ur-type"
  378. try: a = obj._marshalAttrs(ns_map, self)
  379. except: a = ''
  380. ens, edecl = self.genns(ns_map, NS.ENC)
  381. ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI)
  382. self.out.append(
  383. '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
  384. (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
  385. self.genroot(ns_map), id, a))
  386. typed = not same_type
  387. try: elemsname = obj._elemsname
  388. except: elemsname = "item"
  389. for i in data:
  390. self.dump(i, elemsname, typed, ns_map)
  391. self.out.append('</%s>\n' % tag)
  392. dump_tuple = dump_list
  393. def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}):
  394. if Config.debug: print "In dump_dictionary."
  395. tag = tag or self.gentag()
  396. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  397. id = self.checkref(obj, tag, ns_map)
  398. if id == None:
  399. return
  400. try: a = obj._marshalAttrs(ns_map, self)
  401. except: a = ''
  402. self.out.append('<%s%s%s%s>\n' %
  403. (tag, id, a, self.genroot(ns_map)))
  404. for (k, v) in obj.items():
  405. if k[0] != "_":
  406. self.dump(v, k, 1, ns_map)
  407. self.out.append('</%s>\n' % tag)
  408. dump_dict = dump_dictionary # For Python 2.2+
  409. def dump_instance(self, obj, tag, typed = 1, ns_map = {}):
  410. if Config.debug: print "In dump_instance.", "obj=", obj, "tag=", tag
  411. if not tag:
  412. # If it has a name use it.
  413. if isinstance(obj, anyType) and obj._name:
  414. tag = obj._name
  415. else:
  416. tag = self.gentag()
  417. tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
  418. if isinstance(obj, arrayType): # Array
  419. self.dump_list(obj, tag, typed, ns_map)
  420. return
  421. if isinstance(obj, faultType): # Fault
  422. cns, cdecl = self.genns(ns_map, NS.ENC)
  423. vns, vdecl = self.genns(ns_map, NS.ENV)
  424. self.out.append('''<%sFault %sroot="1"%s%s>
  425. <faultcode>%s</faultcode>
  426. <faultstring>%s</faultstring>
  427. ''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring))
  428. if hasattr(obj, "detail"):
  429. self.dump(obj.detail, "detail", typed, ns_map)
  430. self.out.append("</%sFault>\n" % vns)
  431. return
  432. r = self.genroot(ns_map)
  433. try: a = obj._marshalAttrs(ns_map, self)
  434. except: a = ''
  435. if isinstance(obj, voidType): # void
  436. self.out.append("<%s%s%s></%s>\n" % (tag, a, r, tag))
  437. return
  438. id = self.checkref(obj, tag, ns_map)
  439. if id == None:
  440. return
  441. if isinstance(obj, structType):
  442. # Check for namespace
  443. ndecl = ''
  444. ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
  445. self.config.strictNamespaces)
  446. if ns:
  447. ns, ndecl = self.genns(ns_map, ns)
  448. tag = ns + tag
  449. self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r))
  450. keylist = obj.__dict__.keys()
  451. # first write out items with order information
  452. if hasattr(obj, '_keyord'):
  453. for i in range(len(obj._keyord)):
  454. self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map)
  455. keylist.remove(obj._keyord[i])
  456. # now write out the rest
  457. for k in keylist:
  458. if (k[0] != "_"):
  459. self.dump(getattr(obj,k), k, 1, ns_map)
  460. if isinstance(obj, bodyType):
  461. self.multis = 1
  462. for v, k in self.multirefs:
  463. self.dump(v, k, typed = typed, ns_map = ns_map)
  464. self.out.append('</%s>\n' % tag)
  465. elif isinstance(obj, anyType):
  466. t = ''
  467. if typed:
  468. ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
  469. self.config.strictNamespaces)
  470. if ns:
  471. ons, ondecl = self.genns(ns_map, ns)
  472. ins, indecl = self.genns(ns_map,
  473. self.config.schemaNamespaceURI)
  474. t = ' %stype="%s%s"%s%s' % \
  475. (ins, ons, obj._type, ondecl, indecl)
  476. self.out.append('<%s%s%s%s%s>%s</%s>\n' %
  477. (tag, t, id, a, r, obj._marshalData(), tag))
  478. else: # Some Class
  479. self.out.append('<%s%s%s>\n' % (tag, id, r))
  480. for (k, v) in obj.__dict__.items():
  481. if k[0] != "_":
  482. self.dump(v, k, 1, ns_map)
  483. self.out.append('</%s>\n' % tag)
  484. ################################################################################
  485. # SOAPBuilder's more public interface
  486. ################################################################################
  487. def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None,
  488. methodattrs=None,envelope=1,encoding='UTF-8',config=Config,noroot = 0):
  489. t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace,
  490. header=header, methodattrs=methodattrs,envelope=envelope,
  491. encoding=encoding, config=config,noroot=noroot)
  492. return t.build()