05c9ea5c1d
* sleekxmpp no longer spawns threads for callback handlers -- there are now two threads: one for handlers and one for reading. callback handlers can get results from the read queue directly with the "wait" handler which is used in .send() for the reply catching argument.
273 lines
7.4 KiB
Python
273 lines
7.4 KiB
Python
"""
|
|
XEP-0009 XMPP Remote Procedure Calls
|
|
"""
|
|
from __future__ import with_statement
|
|
from . import base
|
|
import logging
|
|
from xml.etree import cElementTree as ET
|
|
import copy
|
|
import time
|
|
import base64
|
|
|
|
def py2xml(*args):
|
|
params = ET.Element("params")
|
|
for x in args:
|
|
param = ET.Element("param")
|
|
param.append(_py2xml(x))
|
|
params.append(param) #<params><param>...
|
|
return params
|
|
|
|
def _py2xml(*args):
|
|
for x in args:
|
|
val = ET.Element("value")
|
|
if type(x) is int:
|
|
i4 = ET.Element("i4")
|
|
i4.text = str(x)
|
|
val.append(i4)
|
|
if type(x) is bool:
|
|
boolean = ET.Element("boolean")
|
|
boolean.text = str(int(x))
|
|
val.append(boolean)
|
|
elif type(x) is str:
|
|
string = ET.Element("string")
|
|
string.text = x
|
|
val.append(string)
|
|
elif type(x) is float:
|
|
double = ET.Element("double")
|
|
double.text = str(x)
|
|
val.append(double)
|
|
elif type(x) is rpcbase64:
|
|
b64 = ET.Element("Base64")
|
|
b64.text = x.encoded()
|
|
val.append(b64)
|
|
elif type(x) is rpctime:
|
|
iso = ET.Element("dateTime.iso8601")
|
|
iso.text = str(x)
|
|
val.append(iso)
|
|
elif type(x) is list:
|
|
array = ET.Element("array")
|
|
data = ET.Element("data")
|
|
for y in x:
|
|
data.append(_py2xml(y))
|
|
array.append(data)
|
|
val.append(array)
|
|
elif type(x) is dict:
|
|
struct = ET.Element("struct")
|
|
for y in x.keys():
|
|
member = ET.Element("member")
|
|
name = ET.Element("name")
|
|
name.text = y
|
|
member.append(name)
|
|
member.append(_py2xml(x[y]))
|
|
struct.append(member)
|
|
val.append(struct)
|
|
return val
|
|
|
|
def xml2py(params):
|
|
vals = []
|
|
for param in params.findall('param'):
|
|
vals.append(_xml2py(param.find('value')))
|
|
return vals
|
|
|
|
def _xml2py(value):
|
|
if value.find('i4') is not None:
|
|
return int(value.find('i4').text)
|
|
if value.find('int') is not None:
|
|
return int(value.find('int').text)
|
|
if value.find('boolean') is not None:
|
|
return bool(value.find('boolean').text)
|
|
if value.find('string') is not None:
|
|
return value.find('string').text
|
|
if value.find('double') is not None:
|
|
return float(value.find('double').text)
|
|
if value.find('Base64') is not None:
|
|
return rpcbase64(value.find('Base64').text)
|
|
if value.find('dateTime.iso8601') is not None:
|
|
return rpctime(value.find('dateTime.iso8601'))
|
|
if value.find('struct') is not None:
|
|
struct = {}
|
|
for member in value.find('struct').findall('member'):
|
|
struct[member.find('name').text] = _xml2py(member.find('value'))
|
|
return struct
|
|
if value.find('array') is not None:
|
|
array = []
|
|
for val in value.find('array').find('data').findall('value'):
|
|
array.append(_xml2py(val))
|
|
return array
|
|
raise ValueError()
|
|
|
|
class rpcbase64(object):
|
|
def __init__(self, data):
|
|
#base 64 encoded string
|
|
self.data = data
|
|
|
|
def decode(self):
|
|
return base64.decodestring(data)
|
|
|
|
def __str__(self):
|
|
return self.decode()
|
|
|
|
def encoded(self):
|
|
return self.data
|
|
|
|
class rpctime(object):
|
|
def __init__(self,data=None):
|
|
#assume string data is in iso format YYYYMMDDTHH:MM:SS
|
|
if type(data) is str:
|
|
self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
|
|
elif type(data) is time.struct_time:
|
|
self.timestamp = data
|
|
elif data is None:
|
|
self.timestamp = time.gmtime()
|
|
else:
|
|
raise ValueError()
|
|
|
|
def iso8601(self):
|
|
#return a iso8601 string
|
|
return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
|
|
|
|
def __str__(self):
|
|
return self.iso8601()
|
|
|
|
class JabberRPCEntry(object):
|
|
def __init__(self,call):
|
|
self.call = call
|
|
self.result = None
|
|
self.error = None
|
|
self.allow = {} #{'<jid>':['<resource1>',...],...}
|
|
self.deny = {}
|
|
|
|
def check_acl(self, jid, resource):
|
|
#Check for deny
|
|
if jid in self.deny.keys():
|
|
if self.deny[jid] == None or resource in self.deny[jid]:
|
|
return False
|
|
#Check for allow
|
|
if allow == None:
|
|
return True
|
|
if jid in self.allow.keys():
|
|
if self.allow[jid] == None or resource in self.allow[jid]:
|
|
return True
|
|
return False
|
|
|
|
def acl_allow(self, jid, resource):
|
|
if jid == None:
|
|
self.allow = None
|
|
elif resource == None:
|
|
self.allow[jid] = None
|
|
elif jid in self.allow.keys():
|
|
self.allow[jid].append(resource)
|
|
else:
|
|
self.allow[jid] = [resource]
|
|
|
|
def acl_deny(self, jid, resource):
|
|
if jid == None:
|
|
self.deny = None
|
|
elif resource == None:
|
|
self.deny[jid] = None
|
|
elif jid in self.deny.keys():
|
|
self.deny[jid].append(resource)
|
|
else:
|
|
self.deny[jid] = [resource]
|
|
|
|
def call_method(self, args):
|
|
ret = self.call(*args)
|
|
|
|
class xep_0009(base.base_plugin):
|
|
|
|
def plugin_init(self):
|
|
self.xep = '0009'
|
|
self.description = 'Jabber-RPC'
|
|
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", self._callMethod)
|
|
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult)
|
|
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError)
|
|
self.entries = {}
|
|
self.activeCalls = []
|
|
|
|
def post_init(self):
|
|
self.xmpp['xep_0030'].add_feature('jabber:iq:rpc')
|
|
self.xmpp['xep_0030'].add_identity('automatition','rpc')
|
|
|
|
def register_call(self, method, name=None):
|
|
#@returns an string that can be used in acl commands.
|
|
with self.lock:
|
|
if name is None:
|
|
self.entries[method.__name__] = JabberRPCEntry(method)
|
|
return method.__name__
|
|
else:
|
|
self.entries[name] = JabberRPCEntry(method)
|
|
return name
|
|
|
|
def acl_allow(self, entry, jid=None, resource=None):
|
|
#allow the method entry to be called by the given jid and resource.
|
|
#if jid is None it will allow any jid/resource.
|
|
#if resource is None it will allow any resource belonging to the jid.
|
|
with self.lock:
|
|
if self.entries[entry]:
|
|
self.entries[entry].acl_allow(jid,resource)
|
|
else:
|
|
raise ValueError()
|
|
|
|
def acl_deny(self, entry, jid=None, resource=None):
|
|
#Note: by default all requests are denied unless allowed with acl_allow.
|
|
#If you deny an entry it will not be allowed regardless of acl_allow
|
|
with self.lock:
|
|
if self.entries[entry]:
|
|
self.entries[entry].acl_deny(jid,resource)
|
|
else:
|
|
raise ValueError()
|
|
|
|
def unregister_call(self, entry):
|
|
#removes the registered call
|
|
with self.lock:
|
|
if self.entries[entry]:
|
|
del self.entries[entry]
|
|
else:
|
|
raise ValueError()
|
|
|
|
def makeMethodCallQuery(self,pmethod,params):
|
|
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
|
|
methodCall = ET.Element('methodCall')
|
|
methodName = ET.Element('methodName')
|
|
methodName.text = pmethod
|
|
methodCall.append(methodName)
|
|
methodCall.append(params)
|
|
query.append(methodCall)
|
|
return query
|
|
|
|
def makeIqMethodCall(self,pto,pmethod,params):
|
|
iq = self.xmpp.makeIqSet()
|
|
iq.set('to',pto)
|
|
iq.append(self.makeMethodCallQuery(pmethod,params))
|
|
return iq
|
|
|
|
def makeIqMethodResponse(self,pto,pid,params):
|
|
iq = self.xmpp.makeIqResult(pid)
|
|
iq.set('to',pto)
|
|
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
|
|
methodResponse = ET.Element('methodResponse')
|
|
methodResponse.append(params)
|
|
query.append(methodResponse)
|
|
return iq
|
|
|
|
def makeIqMethodError(self,pto,id,pmethod,params,condition):
|
|
iq = self.xmpp.makeIqError(id)
|
|
iq.set('to',pto)
|
|
iq.append(self.makeMethodCallQuery(pmethod,params))
|
|
iq.append(self.xmpp['xep_0086'].makeError(condition))
|
|
return iq
|
|
|
|
|
|
|
|
def call_remote(self, pto, pmethod, *args):
|
|
#calls a remote method. Returns the id of the Iq.
|
|
pass
|
|
|
|
def _callMethod(self,xml):
|
|
pass
|
|
|
|
def _callResult(self,xml):
|
|
pass
|
|
|
|
def _callError(self,xml):
|
|
pass
|