core, frontends, bridgen plugins: fixed methods which were unproperly managing multi-profiles

- added profile argument to askConfirmation, actionResult, actionResultExt, entityDataUpdated, confirmationAnswer, getProgress
    - core, frontends: fixed calls/signals according to new bridge API
    - user of proper profile namespace for progression indicators and dialogs
    - memory: getParam* now return bool when param type is bool
    - memory: added getStringParam* to return string instead of typed value
    - core, memory, storage, quick_frontend: getHistory now manage properly multi-profiles
    - plugins XEP-0047, XEP-0054, XEP-0065, XEP-0077, XEP-0096; multi-profiles proper handling
This commit is contained in:
Goffi 2012-11-10 16:38:16 +01:00
commit fa38394401
20 changed files with 570 additions and 400 deletions

View file

@ -21,16 +21,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from logging import debug, info, warning, error
from twisted.words.xish import domish
from twisted.internet import protocol
from twisted.words.protocols.jabber import client, jid
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber import error as jab_error
import os, os.path
from twisted.internet import reactor
import pdb
from sat.core.exceptions import ProfileNotInCacheError
from zope.interface import implements
from wokkel import disco, iwokkel, data_form
from wokkel import data_form
IQ_SET = '/iq[@type="set"]'
PROFILE_NAME = "pipe-transfer"
@ -53,19 +50,23 @@ class Exp_Pipe():
def __init__(self, host):
info(_("Plugin Pipe initialization"))
self.host = host
self._waiting_for_approval = {} #key = id, value = [transfer data, IdelayedCall Reactor timeout,
# current stream method, [failed stream methods], profile]
self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE,
self.host.plugins["XEP-0047"].NAMESPACE] #Stream methods managed
self.host.plugins["XEP-0095"].registerSIProfile(PROFILE_NAME, self.transferRequest)
host.bridge.addMethod("pipeOut", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self.pipeOut)
def _kill_id(self, approval_id):
def profileConnected(self, profile):
client = self.host.getClient(profile)
client._pipe_waiting_for_approval = {} #key = id, value = [transfer data, IdelayedCall Reactor timeout,
# current stream method, [failed stream methods], profile]
def _kill_id(self, approval_id, profile):
"""Delete a waiting_for_approval id, called after timeout
@param approval_id: id of _waiting_for_approval"""
@param approval_id: id of _pipe_waiting_for_approval"""
info(_("SI Pipe Transfer: TimeOut reached for id %s") % approval_id);
try:
del self._waiting_for_approval[approval_id]
client = self.host.getClient(profile)
del client._pipe_waiting_for_approval[approval_id]
except KeyError:
warning(_("kill id called on a non existant approval id"))
@ -79,6 +80,9 @@ class Exp_Pipe():
@param profile: %(doc_profile)s"""
info (_("EXP-PIPE file transfer requested"))
debug(si_el.toXml())
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
pipe_elts = filter(lambda elt: elt.name == 'pipe', si_el.elements())
feature_elts = self.host.plugins["XEP-0020"].getFeatureElt(si_el)
@ -107,17 +111,20 @@ class Exp_Pipe():
#if we are here, the transfer can start, we just need user's agreement
data={ "id": iq_id, "from":from_jid }
self._waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id), stream_method, [], profile]
client._pipe_waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id), stream_method, [], profile]
self.host.askConfirmation(si_id, "PIPE_TRANSFER", data, self.confirmationCB)
self.host.askConfirmation(si_id, "PIPE_TRANSFER", data, self.confirmationCB, profile)
def confirmationCB(self, sid, accepted, frontend_data):
def confirmationCB(self, sid, accepted, frontend_data, profile):
"""Called on confirmation answer
@param sid: file transfer session id
@param accepted: True if file transfer is accepted
@param frontend_data: data sent by frontend"""
data, timeout, stream_method, failed_methods, profile = self._waiting_for_approval[sid]
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data, timeout, stream_method, failed_methods, profile = client._pipe_waiting_for_approval[sid]
if accepted:
if timeout.active():
timeout.cancel()
@ -125,17 +132,17 @@ class Exp_Pipe():
dest_path = frontend_data['dest_path']
except KeyError:
error(_('dest path not found in frontend_data'))
del(self._waiting_for_approval[sid])
del(client._pipe_waiting_for_approval[sid])
return
if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
file_obj = open(dest_path, 'w+')
self.host.plugins["XEP-0065"].prepareToReceive(jid.JID(data['from']), sid, file_obj, None, self._transferSucceeded, self._transferFailed)
self.host.plugins["XEP-0065"].prepareToReceive(jid.JID(data['from']), sid, file_obj, None, self._transferSucceeded, self._transferFailed, profile)
elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
file_obj = open(dest_path, 'w+')
self.host.plugins["XEP-0047"].prepareToReceive(jid.JID(data['from']), sid, file_obj, None, self._transferSucceeded, self._transferFailed)
self.host.plugins["XEP-0047"].prepareToReceive(jid.JID(data['from']), sid, file_obj, None, self._transferSucceeded, self._transferFailed, profile)
else:
error(_("Unknown stream method, this should not happen at this stage, cancelling transfer"))
del(self._waiting_for_approval[sid])
del(client._pipe_waiting_for_approval[sid])
return
#we can send the iq result
@ -146,29 +153,35 @@ class Exp_Pipe():
else:
debug (_("Transfer [%s] refused"), sid)
self.host.plugins["XEP-0095"].sendRejectedError (data["id"], data['from'], profile=profile)
del(self._waiting_for_approval[sid])
del(client._pipe_waiting_for_approval[sid])
def _transferSucceeded(self, sid, file_obj, stream_method):
def _transferSucceeded(self, sid, file_obj, stream_method, profile):
"""Called by the stream method when transfer successfuly finished
@param id: stream id"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
file_obj.close()
info(_('Transfer %s successfuly finished') % sid)
del(self._waiting_for_approval[sid])
del(client._pipe_waiting_for_approval[sid])
def _transferFailed(self, sid, file_obj, stream_method, reason):
def _transferFailed(self, sid, file_obj, stream_method, reason, profile):
"""Called when something went wrong with the transfer
@param id: stream id
@param reason: can be TIMEOUT, IO_ERROR, PROTOCOL_ERROR"""
data, timeout, stream_method, failed_methods, profile = self._waiting_for_approval[sid]
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data, timeout, stream_method, failed_methods, profile = client._pipe_waiting_for_approval[sid]
warning(_('Transfer %(id)s failed with stream method %(s_method)s') % { 'id': sid,
's_method': stream_method })
filepath = file_obj.name
file_obj.close()
#TODO: session remenber (within a time limit) when a stream method fail, and avoid that stream method with full jid for the rest of the session
warning(_("All stream methods failed, can't transfer the file"))
del(self._waiting_for_approval[sid])
del(client._pipe_waiting_for_approval[sid])
def pipeCb(self, profile, filepath, sid, IQ):
def pipeCb(self, filepath, sid, profile, IQ):
if IQ['type'] == "error":
stanza_err = jab_error.exceptionFromStanza(IQ)
if stanza_err.code == '403' and stanza_err.condition == 'forbidden':
@ -230,13 +243,14 @@ class Exp_Pipe():
pipe_transfer_elts.append(pipe_elt)
sid, offer = self.host.plugins["XEP-0095"].proposeStream(jid.JID(to_jid), PROFILE, feature_elt, pipe_transfer_elts, profile_key = profile)
offer.addCallback(self.pipeCb, profile, filepath, sid)
offer.addCallback(self.pipeCb, filepath, sid, profile)
return sid
def sendSuccessCb(self, sid, file_obj, stream_method):
def sendSuccessCb(self, sid, file_obj, stream_method, profile):
info(_('Transfer %s successfuly finished') % sid)
file_obj.close()
def sendFailureCb(self, sid, file_obj, stream_method, reason):
def sendFailureCb(self, sid, file_obj, stream_method, reason, profile):
file_obj.close()
warning(_('Transfer %(id)s failed with stream method %(s_method)s') % { 'id': sid, "s_method": stream_method })
warning(_('Transfer %(id)s failed with stream method %(s_method)s %(profile)s') % { 'id': sid, "s_method": stream_method, "profile": profile })

View file

@ -74,7 +74,7 @@ class XmlLog():
#bridge
host.bridge.addSignal("xmlLog", ".plugin", signature='sss') #args: direction("IN" or "OUT"), xml_data, profile
do_log = bool(self.host.memory.getParamA("Xml log", "Debug"))
do_log = self.host.memory.getParamA("Xml log", "Debug")
if do_log:
info(_("XML log activated"))
host.trigger.add("XML Initialized", self.logXml)

View file

@ -20,11 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from logging import debug, info, warning, error
from twisted.words.protocols.jabber import client, jid
from twisted.words.protocols.jabber import error as jab_error
from twisted.words.protocols.jabber import client as jabber_client, jid
from twisted.words.xish import domish
import twisted.internet.error
from twisted.internet import reactor
from sat.core.exceptions import ProfileNotInCacheError
from wokkel import disco, iwokkel
@ -63,66 +63,82 @@ class XEP_0047():
def __init__(self, host):
info(_("In-Band Bytestreams plugin initialization"))
self.host = host
self.current_stream = {} #key: stream_id, value: data(dict)
def getHandler(self, profile):
return XEP_0047_handler(self)
def _timeOut(self, sid):
"""Delecte current_stream id, called after timeout
@param id: id of self.current_stream"""
info(_("In-Band Bytestream: TimeOut reached for id %s") % sid);
self._killId(sid, False, "TIMEOUT")
def profileConnected(self, profile):
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
client.xep_0047_current_stream = {} #key: stream_id, value: data(dict)
def _killId(self, sid, success=False, failure_reason="UNKNOWN"):
def _timeOut(self, sid, profile):
"""Delecte current_stream id, called after timeout
@param id: id of client.xep_0047_current_stream"""
info(_("In-Band Bytestream: TimeOut reached for id %s [%s]") % (sid, profile));
self._killId(sid, False, "TIMEOUT", profile)
def _killId(self, sid, success=False, failure_reason="UNKNOWN", profile=None):
"""Delete an current_stream id, clean up associated observers
@param sid: id of self.current_stream"""
if not self.current_stream.has_key(sid):
@param sid: id of client.xep_0047_current_stream"""
assert(profile)
client = self.host.getClient(profile)
if not client:
warning(_("Client no more in cache"))
return
if not client.xep_0047_current_stream.has_key(sid):
warning(_("kill id called on a non existant id"))
return
if self.current_stream[sid].has_key("observer_cb"):
xmlstream = self.current_stream[sid]["xmlstream"]
xmlstream.removeObserver(self.current_stream[sid]["event_data"], self.current_stream[sid]["observer_cb"])
if self.current_stream[sid]['timer'].active():
self.current_stream[sid]['timer'].cancel()
if self.current_stream[sid].has_key("size"):
self.host.removeProgressCB(sid)
if client.xep_0047_current_stream[sid].has_key("observer_cb"):
client.xmlstream.removeObserver(client.xep_0047_current_stream[sid]["event_data"], client.xep_0047_current_stream[sid]["observer_cb"])
if client.xep_0047_current_stream[sid]['timer'].active():
client.xep_0047_current_stream[sid]['timer'].cancel()
if client.xep_0047_current_stream[sid].has_key("size"):
self.host.removeProgressCB(sid, profile)
file_obj = self.current_stream[sid]['file_obj']
success_cb = self.current_stream[sid]['success_cb']
failure_cb = self.current_stream[sid]['failure_cb']
file_obj = client.xep_0047_current_stream[sid]['file_obj']
success_cb = client.xep_0047_current_stream[sid]['success_cb']
failure_cb = client.xep_0047_current_stream[sid]['failure_cb']
del self.current_stream[sid]
del client.xep_0047_current_stream[sid]
if success:
success_cb(sid, file_obj, NS_IBB)
success_cb(sid, file_obj, NS_IBB, profile)
else:
failure_cb(sid, file_obj, NS_IBB, failure_reason)
failure_cb(sid, file_obj, NS_IBB, failure_reason, profile)
def getProgress(self, sid, data):
def getProgress(self, sid, data, profile):
"""Fill data with position of current transfer"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
try:
file_obj = self.current_stream[sid]["file_obj"]
file_obj = client.xep_0047_current_stream[sid]["file_obj"]
data["position"] = str(file_obj.tell())
data["size"] = str(self.current_stream[sid]["size"])
data["size"] = str(client.xep_0047_current_stream[sid]["size"])
except:
pass
def prepareToReceive(self, from_jid, sid, file_obj, size, success_cb, failure_cb):
def prepareToReceive(self, from_jid, sid, file_obj, size, success_cb, failure_cb, profile):
"""Called when a bytestream is imminent
@param from_jid: jid of the sender
@param sid: Stream id
@param file_obj: File object where data will be written
@param size: full size of the data, or None if unknown
@param success_cb: method to call when successfuly finished
@param failure_cb: method to call when something goes wrong"""
data = self.current_stream[sid] = {}
@param failure_cb: method to call when something goes wrong
@param profile: %(doc_profile)s"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data = client.xep_0047_current_stream[sid] = {}
data["from"] = from_jid
data["file_obj"] = file_obj
data["seq"] = -1
if size:
data["size"] = size
self.host.registerProgressCB(sid, self.getProgress)
self.host.registerProgressCB(sid, self.getProgress, profile)
data["timer"] = reactor.callLater(TIMEOUT, self._timeOut, sid)
data["success_cb"] = success_cb
data["failure_cb"] = failure_cb
@ -130,47 +146,51 @@ class XEP_0047():
def streamOpening(self, IQ, profile):
debug(_("IBB stream opening"))
IQ.handled=True
profile_jid, xmlstream = self.host.getJidNStream(profile)
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
open_elt = IQ.firstChildElement()
block_size = open_elt.getAttribute('block-size')
sid = open_elt.getAttribute('sid')
stanza = open_elt.getAttribute('stanza', 'iq')
if not sid or not block_size or int(block_size)>65535:
warning(_("malformed IBB transfer: %s" % IQ['id']))
self.sendNotAcceptableError(IQ['id'], IQ['from'], xmlstream)
self.sendNotAcceptableError(IQ['id'], IQ['from'], client.xmlstream)
return
if not sid in self.current_stream:
if not sid in client.xep_0047_current_stream:
warning(_("Ignoring unexpected IBB transfer: %s" % sid))
self.sendNotAcceptableError(IQ['id'], IQ['from'], xmlstream)
self.sendNotAcceptableError(IQ['id'], IQ['from'], client.xmlstream)
return
if self.current_stream[sid]["from"] != jid.JID(IQ['from']):
if client.xep_0047_current_stream[sid]["from"] != jid.JID(IQ['from']):
warning(_("sended jid inconsistency (man in the middle attack attempt ?)"))
self.sendNotAcceptableError(IQ['id'], IQ['from'], xmlstream)
self._killId(sid, False, "PROTOCOL_ERROR")
self.sendNotAcceptableError(IQ['id'], IQ['from'], client.xmlstream)
self._killId(sid, False, "PROTOCOL_ERROR", profile=profile)
return
#at this stage, the session looks ok and will be accepted
#we reset the timeout:
self.current_stream[sid]["timer"].reset(TIMEOUT)
client.xep_0047_current_stream[sid]["timer"].reset(TIMEOUT)
#we save the xmlstream, events and observer data to allow observer removal
self.current_stream[sid]["xmlstream"] = xmlstream
self.current_stream[sid]["event_data"] = event_data = (IBB_MESSAGE_DATA if stanza=='message' else IBB_IQ_DATA) % sid
self.current_stream[sid]["observer_cb"] = observer_cb = self.messageData if stanza=='message' else self.iqData
client.xep_0047_current_stream[sid]["event_data"] = event_data = (IBB_MESSAGE_DATA if stanza=='message' else IBB_IQ_DATA) % sid
client.xep_0047_current_stream[sid]["observer_cb"] = observer_cb = self.messageData if stanza=='message' else self.iqData
event_close = IBB_CLOSE % sid
#we now set the stream observer to look after data packet
xmlstream.addObserver(event_data, observer_cb, profile = profile)
xmlstream.addOnetimeObserver(event_close, self.streamClosing, profile = profile)
client.xmlstream.addObserver(event_data, observer_cb, profile = profile)
client.xmlstream.addOnetimeObserver(event_close, self.streamClosing, profile = profile)
#finally, we send the accept stanza
result = domish.Element((None, 'iq'))
result['type'] = 'result'
result['id'] = IQ['id']
result['to'] = IQ['from']
xmlstream.send(result)
client.xmlstream.send(result)
def streamClosing(self, IQ, profile):
IQ.handled=True
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
debug(_("IBB stream closing"))
data_elt = IQ.firstChildElement()
sid = data_elt.getAttribute('sid')
@ -178,55 +198,60 @@ class XEP_0047():
result['type'] = 'result'
result['id'] = IQ['id']
result['to'] = IQ['from']
self.current_stream[sid]["xmlstream"].send(result)
self._killId(sid, success=True)
client.xmlstream.send(result)
self._killId(sid, success=True, profile=profile)
def iqData(self, IQ, profile):
IQ.handled=True
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data_elt = IQ.firstChildElement()
if self._manageDataElt(data_elt, 'iq', IQ['id'], jid.JID(IQ['from'])):
if self._manageDataElt(data_elt, 'iq', IQ['id'], jid.JID(IQ['from']), profile):
#and send a success answer
result = domish.Element((None, 'iq'))
result['type'] = 'result'
result['id'] = IQ['id']
result['to'] = IQ['from']
_jid, xmlstream = self.host.getJidNStream(profile)
xmlstream.send(result)
client.xmlstream.send(result)
def messageData(self, message_elt, profile):
data_elt = message_elt.firstChildElement()
sid = message_elt.getAttribute('id','')
self._manageDataElt(message_elt, 'message', sid, jid.JID(message_elt['from']))
self._manageDataElt(message_elt, 'message', sid, jid.JID(message_elt['from']), profile)
def _manageDataElt(self, data_elt, stanza, sid, stanza_from_jid):
def _manageDataElt(self, data_elt, stanza, sid, stanza_from_jid, profile):
"""Manage the data elelement (check validity and write to the file_obj)
@param data_elt: "data" domish element
@return: True if success"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
sid = data_elt.getAttribute('sid')
if sid not in self.current_stream:
if sid not in client.xep_0047_current_stream:
error(_("Received data for an unknown session id"))
return False
xmlstream = self.current_stream[sid]["xmlstream"]
from_jid = self.current_stream[sid]["from"]
file_obj = self.current_stream[sid]["file_obj"]
from_jid = client.xep_0047_current_stream[sid]["from"]
file_obj = client.xep_0047_current_stream[sid]["file_obj"]
if stanza_from_jid != from_jid:
warning(_("sended jid inconsistency (man in the middle attack attempt ?)"))
if stanza=='iq':
self.sendNotAcceptableError(sid, from_jid, xmlstream)
self.sendNotAcceptableError(sid, from_jid, client.xmlstream)
return False
self.current_stream[sid]["seq"]+=1
if int(data_elt.getAttribute("seq",-1)) != self.current_stream[sid]["seq"]:
client.xep_0047_current_stream[sid]["seq"]+=1
if int(data_elt.getAttribute("seq",-1)) != client.xep_0047_current_stream[sid]["seq"]:
warning(_("Sequence error"))
if stanza=='iq':
self.sendNotAcceptableError(IQ["id"], from_jid, xmlstream)
self.sendNotAcceptableError(data_elt["id"], from_jid, client.xmlstream)
return False
#we reset the timeout:
self.current_stream[sid]["timer"].reset(TIMEOUT)
client.xep_0047_current_stream[sid]["timer"].reset(TIMEOUT)
#we can now decode the data
try:
@ -235,7 +260,7 @@ class XEP_0047():
#The base64 data is invalid
warning(_("Invalid base64 data"))
if stanza=='iq':
self.sendNotAcceptableError(IQ["id"], from_jid, xmlstream)
self.sendNotAcceptableError(data_elt["id"], from_jid, client.xmlstream)
return False
return True
@ -253,7 +278,7 @@ class XEP_0047():
error_el.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas','not-acceptable'))
xmlstream.send(result)
def startStream(self, file_obj, to_jid, sid, length, successCb, failureCb, size = None, profile='@NONE@'):
def startStream(self, file_obj, to_jid, sid, length, successCb, failureCb, size = None, profile=None):
"""Launch the stream workflow
@param file_obj: file_obj to send
@param to_jid: JID of the recipient
@ -262,34 +287,38 @@ class XEP_0047():
@param successCb: method to call when stream successfuly finished
@param failureCb: method to call when something goes wrong
@param profile: %(doc_profile)s"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
if length != None:
error(_('stream length not managed yet'))
return;
profile_jid, xmlstream = self.host.getJidNStream(profile)
data = self.current_stream[sid] = {}
data = client.xep_0047_current_stream[sid] = {}
data["timer"] = reactor.callLater(TIMEOUT, self._timeOut, sid)
data["file_obj"] = file_obj
data["to"] = to_jid
data["success_cb"] = successCb
data["failure_cb"] = failureCb
data["xmlstream"] = xmlstream
data["block_size"] = BLOCK_SIZE
if size:
data["size"] = size
self.host.registerProgressCB(sid, self.getProgress)
iq_elt = client.IQ(xmlstream,'set')
iq_elt['from'] = profile_jid.full()
self.host.registerProgressCB(sid, self.getProgress, profile)
iq_elt = jabber_client.IQ(client.xmlstream,'set')
iq_elt['from'] = client.jid.full()
iq_elt['to'] = to_jid.full()
open_elt = iq_elt.addElement('open',NS_IBB)
open_elt['block-size'] = str(BLOCK_SIZE)
open_elt['sid'] = sid
open_elt['stanza'] = 'iq'
iq_elt.addCallback(self.iqResult, sid, 0, length)
iq_elt.addCallback(self.iqResult, sid, 0, length, profile)
iq_elt.send()
def iqResult(self, sid, seq, length, iq_elt):
def iqResult(self, sid, seq, length, profile, iq_elt):
"""Called when the result of open iq is received"""
data = self.current_stream[sid]
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data = client.xep_0047_current_stream[sid]
if iq_elt["type"] == "error":
warning(_("Transfer failed"))
self.terminateStream(sid, "IQ_ERROR")
@ -300,18 +329,18 @@ class XEP_0047():
buffer = data["file_obj"].read(data["block_size"])
if buffer:
next_iq_elt = client.IQ(data["xmlstream"],'set')
next_iq_elt = jabber_client.IQ(client.xmlstream,'set')
next_iq_elt['to'] = data["to"].full()
data_elt = next_iq_elt.addElement('data', NS_IBB)
data_elt['seq'] = str(seq)
data_elt['sid'] = sid
data_elt.addContent(base64.b64encode(buffer))
next_iq_elt.addCallback(self.iqResult, sid, seq+1, length)
next_iq_elt.addCallback(self.iqResult, sid, seq+1, length, profile)
next_iq_elt.send()
else:
self.terminateStream(sid)
self.terminateStream(sid, profile=profile)
def terminateStream(self, sid, failure_reason = None):
def terminateStream(self, sid, failure_reason = None, profile=None):
"""Terminate the stream session
@param to_jid: recipient
@param sid: Session id
@ -320,17 +349,20 @@ class XEP_0047():
@param progress_cb: True if we have to remove the progress callback
@param callback: method to call after finishing
@param failure_reason: reason of the failure, or None if steam was successful"""
data = self.current_stream[sid]
iq_elt = client.IQ(data["xmlstream"],'set')
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data = client.xep_0047_current_stream[sid]
iq_elt = jabber_client.IQ(client.xmlstream,'set')
iq_elt['to'] = data["to"].full()
close_elt = iq_elt.addElement('close',NS_IBB)
close_elt['sid'] = sid
iq_elt.send()
self.host.removeProgressCB(sid)
self.host.removeProgressCB(sid, profile)
if failure_reason:
self._killId(sid, False, failure_reason)
self._killId(sid, False, failure_reason, profile=profile)
else:
self._killId(sid, True)
self._killId(sid, True, profile=profile)
class XEP_0047_handler(XMPPHandler):
implements(iwokkel.IDisco)

View file

@ -157,15 +157,15 @@ class XEP_0054():
if answer.firstChildElement().name == "vCard":
d = self.vCard2Dict(answer.firstChildElement(), jid.JID(answer["from"]), profile)
d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data))
d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data, profile))
else:
error (_("FIXME: vCard not found as first child element"))
self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be better
self.host.bridge.actionResult("SUPPRESS", answer['id'], {}, profile) #FIXME: maybe an error message would be better
def vcard_err(self, failure):
def vcard_err(self, failure, profile):
"""Called when something is wrong with registration"""
error (_("Can't find VCard of %s") % failure.value.stanza['from'])
self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}) #FIXME: maybe an error message would be better
self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}, profile) #FIXME: maybe an error message would be better
def getCard(self, target, profile_key='@DEFAULT@'):
"""Ask server for VCard
@ -182,7 +182,7 @@ class XEP_0054():
reg_request["from"]=current_jid.full()
reg_request["to"] = to_jid.userhost()
reg_request.addElement('vCard', NS_VCARD)
reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, [profile])
reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, callbackArgs=[profile], errbackArgs=[profile])
return reg_request["id"]
def getAvatarFile(self, hash):

View file

@ -58,13 +58,13 @@ THE SOFTWARE.
from logging import debug, info, warning, error
from twisted.internet import protocol, reactor
from twisted.internet import error as jab_error
from twisted.words.protocols.jabber import client, jid
from twisted.words.protocols.jabber import jid, client as jabber_client
from twisted.protocols.basic import FileSender
from twisted.words.xish import domish
from twisted.web.client import getPage
from sat.core.exceptions import ProfileNotInCacheError
import struct
import urllib
import hashlib, pdb
import hashlib
from zope.interface import implements
@ -298,10 +298,10 @@ class SOCKSv5(protocol.Protocol, FileSender):
if self.factory.proxy:
self.state = STATE_READY
self.factory.activateCb(self.sid, self.factory.iq_id, self.startTransfer)
self.factory.activateCb(self.sid, self.factory.iq_id, self.startTransfer, self.profile)
else:
self.state = STATE_TARGET_READY
self.factory.activateCb(self.sid, self.factory.iq_id)
self.factory.activateCb(self.sid, self.factory.iq_id, self.profile)
except struct.error, why:
return None
@ -311,6 +311,7 @@ class SOCKSv5(protocol.Protocol, FileSender):
if isinstance(self.factory, Socks5ClientFactory):
self.sid = self.factory.sid
self.profile = self.factory.profile
self.data = self.factory.data
self.state = STATE_TARGET_INITIAL
self._startNegotiation()
@ -318,13 +319,16 @@ class SOCKSv5(protocol.Protocol, FileSender):
def connectRequested(self, addr, port):
debug("connectRequested")
# Check that this session if expected
# Check that this session is expected
if not self.factory.hash_sid_map.has_key(addr):
#no: we refuse it
self.sendErrorReply(socks5.REPLY_CONN_REFUSED)
self.sendErrorReply(REPLY_CONN_REFUSED)
return
self.sid = self.factory.hash_sid_map[addr]
self.factory.current_stream[self.sid]["start_transfer_cb"] = self.startTransfer
self.sid, self.profile = self.factory.hash_sid_map[addr]
client = self.factory.host.getClient(self.profile)
if not client:
raise ProfileNotInCacheError
client.xep_0065_current_stream[self.sid]["start_transfer_cb"] = self.startTransfer
self.connectCompleted(addr, 0)
self.transport.stopReading()
@ -336,7 +340,7 @@ class SOCKSv5(protocol.Protocol, FileSender):
def fileTransfered(self, d):
info(_("File transfer completed, closing connection"))
self.transport.loseConnection()
self.factory.finishedCb(self.sid, True)
self.factory.finishedCb(self.sid, True, self.profile)
def connectCompleted(self, remotehost, remoteport):
debug("connectCompleted")
@ -395,8 +399,8 @@ class SOCKSv5(protocol.Protocol, FileSender):
class Socks5ServerFactory(protocol.ServerFactory):
protocol = SOCKSv5
def __init__(self, current_stream, hash_sid_map, finishedCb):
self.current_stream = current_stream
def __init__(self, host, hash_sid_map, finishedCb):
self.host = host
self.hash_sid_map = hash_sid_map
self.finishedCb = finishedCb
@ -409,27 +413,30 @@ class Socks5ServerFactory(protocol.ServerFactory):
class Socks5ClientFactory(protocol.ClientFactory):
protocol = SOCKSv5
def __init__(self, current_stream, sid, iq_id, activateCb, finishedCb, proxy=False):
def __init__(self, current_stream, sid, iq_id, activateCb, finishedCb, proxy=False, profile=None):
"""Init the Client Factory
@param current_stream: current streams data
@param sid: Session ID
@param iq_id: iq id used to initiate the stream
@param activateCb: method to call to activate the stream
@param finishedCb: method to call when the stream session is finished
@param proxy: True if we are connecting throught a proxy (and we are a requester)"""
@param proxy: True if we are connecting throught a proxy (and we are a requester)
@param profile: %(doc_profile)s"""
assert(profile)
self.data = current_stream[sid]
self.sid = sid
self.iq_id = iq_id
self.activateCb = activateCb
self.finishedCb = finishedCb
self.proxy = proxy
self.profile = profile
def startedConnecting(self, connector):
debug (_("Socks 5 client connection started"))
def clientConnectionLost(self, connector, reason):
debug (_("Socks 5 client connection lost"))
self.finishedCb(self.sid, reason.type == jab_error.ConnectionDone) #TODO: really check if the state is actually successful
debug (_("Socks 5 client connection lost (reason: %s)"), reason)
self.finishedCb(self.sid, reason.type == jab_error.ConnectionDone, self.profile) #TODO: really check if the state is actually successful
class XEP_0065():
@ -458,12 +465,11 @@ class XEP_0065():
info(_("Plugin XEP_0065 initialization"))
#session data
self.current_stream = {} #key: stream_id, value: data(dict)
self.hash_sid_map = {} #key: hash of the transfer session, value: session id
self.hash_sid_map = {} #key: hash of the transfer session, value: (session id, profile)
self.host = host
debug(_("registering"))
self.server_factory = Socks5ServerFactory(self.current_stream, self.hash_sid_map, self._killId)
self.server_factory = Socks5ServerFactory(host, self.hash_sid_map, lambda sid, success, profile: self._killId(sid, success, profile=profile))
#parameters
host.memory.importParams(XEP_0065.params)
@ -476,53 +482,69 @@ class XEP_0065():
def getHandler(self, profile):
return XEP_0065_handler(self)
def profileConnected(self, profile):
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
client.xep_0065_current_stream = {} #key: stream_id, value: data(dict)
def getExternalIP(self):
"""Return IP visible from outside, by asking to a website"""
return getPage("http://www.goffi.org/sat_tools/get_ip.php")
def getProgress(self, sid, data):
def getProgress(self, sid, data, profile):
"""Fill data with position of current transfer"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
try:
file_obj = self.current_stream[sid]["file_obj"]
file_obj = client.xep_0065_current_stream[sid]["file_obj"]
data["position"] = str(file_obj.tell())
data["size"] = str(self.current_stream[sid]["size"])
data["size"] = str(client.xep_0065_current_stream[sid]["size"])
except:
pass
def _timeOut(self, sid):
def _timeOut(self, sid, profile):
"""Delecte current_stream id, called after timeout
@param id: id of self.current_stream"""
info(_("Socks5 Bytestream: TimeOut reached for id %s") % sid);
self._killId(sid, False, "TIMEOUT")
@param id: id of client.xep_0065_current_stream"""
info(_("Socks5 Bytestream: TimeOut reached for id %s [%s]") % (sid, profile));
self._killId(sid, False, "TIMEOUT", profile)
def _killId(self, sid, success=False, failure_reason="UNKNOWN"):
def _killId(self, sid, success=False, failure_reason="UNKNOWN", profile=None):
"""Delete an current_stream id, clean up associated observers
@param sid: id of self.current_stream"""
if not self.current_stream.has_key(sid):
@param sid: id of client.xep_0065_current_stream"""
assert(profile)
client = self.host.getClient(profile)
if not client:
warning(_("Client no more in cache"))
return
if not client.xep_0065_current_stream.has_key(sid):
warning(_("kill id called on a non existant id"))
return
if self.current_stream[sid].has_key("observer_cb"):
xmlstream = self.current_stream[sid]["xmlstream"]
xmlstream.removeObserver(self.current_stream[sid]["event_data"], self.current_stream[sid]["observer_cb"])
if self.current_stream[sid]['timer'].active():
self.current_stream[sid]['timer'].cancel()
if self.current_stream[sid].has_key("size"):
self.host.removeProgressCB(sid)
if client.xep_0065_current_stream[sid].has_key("observer_cb"):
xmlstream = client.xep_0065_current_stream[sid]["xmlstream"]
xmlstream.removeObserver(client.xep_0065_current_stream[sid]["event_data"], client.xep_0065_current_stream[sid]["observer_cb"])
if client.xep_0065_current_stream[sid]['timer'].active():
client.xep_0065_current_stream[sid]['timer'].cancel()
if client.xep_0065_current_stream[sid].has_key("size"):
self.host.removeProgressCB(sid, profile)
file_obj = self.current_stream[sid]['file_obj']
success_cb = self.current_stream[sid]['success_cb']
failure_cb = self.current_stream[sid]['failure_cb']
file_obj = client.xep_0065_current_stream[sid]['file_obj']
success_cb = client.xep_0065_current_stream[sid]['success_cb']
failure_cb = client.xep_0065_current_stream[sid]['failure_cb']
del self.current_stream[sid]
if self.hash_sid_map.has_key(sid):
del self.hash_sid_map[sid]
session_hash = client.xep_0065_current_stream[sid].get('hash')
del client.xep_0065_current_stream[sid]
if session_hash in self.hash_sid_map:
#FIXME: check that self.hash_sid_map is correctly cleaned in all cases (timeout, normal flow, etc).
del self.hash_sid_map[session_hash]
if success:
success_cb(sid, file_obj, NS_BS)
success_cb(sid, file_obj, NS_BS, profile)
else:
failure_cb(sid, file_obj, NS_BS, failure_reason)
failure_cb(sid, file_obj, NS_BS, failure_reason, profile)
def startStream(self, file_obj, to_jid, sid, length, successCb, failureCb, size = None, profile='@NONE@'):
def startStream(self, file_obj, to_jid, sid, length, successCb, failureCb, size = None, profile=None):
"""Launch the stream workflow
@param file_obj: file_obj to send
@param to_jid: JID of the recipient
@ -531,16 +553,21 @@ class XEP_0065():
@param successCb: method to call when stream successfuly finished
@param failureCb: method to call when something goes wrong
@param profile: %(doc_profile)s"""
assert(profile)
client = self.host.getClient(profile)
if not client:
error(_("Unknown profile, this should not happen"))
raise ProfileNotInCacheError
if length != None:
error(_('stream length not managed yet'))
return;
profile_jid, xmlstream = self.host.getJidNStream(profile)
if not profile_jid or not xmlstream:
error(_("Unknown profile, this should not happen"))
return;
data = self.current_stream[sid] = {}
data["profile"] = profile
data["timer"] = reactor.callLater(TIMEOUT, self._timeOut, sid)
profile_jid = client.jid
xmlstream = client.xmlstream
data = client.xep_0065_current_stream[sid] = {}
data["timer"] = reactor.callLater(TIMEOUT, self._timeOut, sid, profile)
data["file_obj"] = file_obj
data["from"] = profile_jid
data["to"] = to_jid
@ -548,11 +575,11 @@ class XEP_0065():
data["failure_cb"] = failureCb
data["xmlstream"] = xmlstream
data["hash"] = calculateHash(profile_jid, to_jid, sid)
self.hash_sid_map[data["hash"]] = sid
self.hash_sid_map[data["hash"]] = (sid, profile)
if size:
data["size"] = size
self.host.registerProgressCB(sid, self.getProgress)
iq_elt = client.IQ(xmlstream,'set')
self.host.registerProgressCB(sid, self.getProgress, profile)
iq_elt = jabber_client.IQ(xmlstream,'set')
iq_elt["from"] = profile_jid.full()
iq_elt["to"] = to_jid.full()
query_elt = iq_elt.addElement('query', NS_BS)
@ -570,20 +597,21 @@ class XEP_0065():
streamhost['port'] = self.host.memory.getParamA("Proxy port", "File Transfer", profile_key=profile)
streamhost['jid'] = self.host.memory.getParamA("Proxy", "File Transfer", profile_key=profile)
iq_elt.addCallback(self.iqResult, sid)
iq_elt.addCallback(self.iqResult, sid, profile)
iq_elt.send()
def iqResult(self, sid, iq_elt):
def iqResult(self, sid, profile, iq_elt):
"""Called when the result of open iq is received"""
if iq_elt["type"] == "error":
warning(_("Transfer failed"))
return
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
try:
data = self.current_stream[sid]
data = client.xep_0065_current_stream[sid]
file_obj = data["file_obj"]
timer = data["timer"]
profile = data["profile"]
except KeyError:
error(_("Internal error, can't do transfer"))
return
@ -607,15 +635,17 @@ class XEP_0065():
if proxy_jid != streamhost_jid:
warning(_("Proxy jid is not the same as in parameters, this should not happen"))
return
factory = Socks5ClientFactory(self.current_stream, sid, None, self.activateProxyStream, self._killId, True)
factory = Socks5ClientFactory(client.xep_0065_current_stream, sid, None, self.activateProxyStream, lambda sid, success, profile: self._killId(sid, success, profile=profile), True, profile)
reactor.connectTCP(proxy_host, int(proxy_port), factory)
else:
data["start_transfer_cb"](file_obj) #We now activate the stream
def activateProxyStream(self, sid, iq_id, start_transfer_cb):
def activateProxyStream(self, sid, iq_id, start_transfer_cb, profile):
debug(_("activating stream"))
data = self.current_stream[sid]
profile = data['profile']
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data = client.xep_0065_current_stream[sid]
profile_jid, xmlstream = self.host.getJidNStream(profile)
iq_elt = client.IQ(xmlstream,'set')
@ -634,22 +664,26 @@ class XEP_0065():
else:
start_transfer_cb(file_obj)
def prepareToReceive(self, from_jid, sid, file_obj, size, success_cb, failure_cb):
def prepareToReceive(self, from_jid, sid, file_obj, size, success_cb, failure_cb, profile):
"""Called when a bytestream is imminent
@param from_jid: jid of the sender
@param sid: Stream id
@param file_obj: File object where data will be written
@param size: full size of the data, or None if unknown
@param success_cb: method to call when successfuly finished
@param failure_cb: method to call when something goes wrong"""
data = self.current_stream[sid] = {}
@param failure_cb: method to call when something goes wrong
@param profile: %(doc_profile)s"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data = client.xep_0065_current_stream[sid] = {}
data["from"] = from_jid
data["file_obj"] = file_obj
data["seq"] = -1
if size:
data["size"] = size
self.host.registerProgressCB(sid, self.getProgress)
data["timer"] = reactor.callLater(TIMEOUT, self._timeOut, sid)
self.host.registerProgressCB(sid, self.getProgress, profile)
data["timer"] = reactor.callLater(TIMEOUT, self._timeOut, sid, profile)
data["success_cb"] = success_cb
data["failure_cb"] = failure_cb
@ -657,20 +691,26 @@ class XEP_0065():
def streamQuery(self, iq_elt, profile):
"""Get file using byte stream"""
debug(_("BS stream query"))
profile_jid, xmlstream = self.host.getJidNStream(profile)
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
xmlstream = client.xmlstream
iq_elt.handled = True
query_elt = iq_elt.firstChildElement()
sid = query_elt.getAttribute("sid")
streamhost_elts = filter(lambda elt: elt.name == 'streamhost', query_elt.elements())
if not sid in self.current_stream:
if not sid in client.xep_0065_current_stream:
warning(_("Ignoring unexpected BS transfer: %s" % sid))
self.sendNotAcceptableError(iq_elt['id'], iq_elt['from'], xmlstream)
return
self.current_stream[sid]['timer'].cancel()
self.current_stream[sid]["to"] = jid.JID(iq_elt["to"])
self.current_stream[sid]["xmlstream"] = xmlstream
client.xep_0065_current_stream[sid]['timer'].cancel()
client.xep_0065_current_stream[sid]["to"] = jid.JID(iq_elt["to"])
client.xep_0065_current_stream[sid]["xmlstream"] = xmlstream
if not streamhost_elts:
warning(_("No streamhost found in stream query %s" % sid))
@ -686,16 +726,19 @@ class XEP_0065():
self.sendBadRequestError(iq_elt['id'], iq_elt['from'], xmlstream)
return
self.current_stream[sid]["streamhost"] = (sh_host, sh_port, sh_jid)
client.xep_0065_current_stream[sid]["streamhost"] = (sh_host, sh_port, sh_jid)
info (_("Stream proposed: host=[%(host)s] port=[%(port)s]") % {'host':sh_host, 'port':sh_port})
factory = Socks5ClientFactory(self.current_stream, sid, iq_elt["id"], self.activateStream, self._killId)
factory = Socks5ClientFactory(client.xep_0065_current_stream, sid, iq_elt["id"], self.activateStream, lambda sid, success, profile: self._killId(sid, success, profile=profile), profile=profile)
reactor.connectTCP(sh_host, int(sh_port), factory)
def activateStream(self, sid, iq_id):
def activateStream(self, sid, iq_id, profile):
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
debug(_("activating stream"))
result = domish.Element((None, 'iq'))
data = self.current_stream[sid]
data = client.xep_0065_current_stream[sid]
result['type'] = 'result'
result['id'] = iq_id
result['from'] = data["to"].full()
@ -769,7 +812,7 @@ class XEP_0065_handler(XMPPHandler):
if not proxy_ent:
debug(_("No proxy found on this server"))
return
iq_elt = client.IQ(self.parent.xmlstream,'get')
iq_elt = jabber_client.IQ(self.parent.xmlstream,'get')
iq_elt["to"] = proxy_ent.full()
query_elt = iq_elt.addElement('query', NS_BS)
iq_elt.addCallback(self._proxyDataResult)

View file

@ -54,7 +54,7 @@ class XEP_0077():
"""Add a callback which is called when registration to target is successful"""
self.triggers[target] = (cb, profile)
def reg_ok(self, answer):
def reg_ok(self, answer, profile):
"""Called after the first get IQ"""
try:
x_elem = filter (lambda x:x.name == "x", answer.firstChildElement().elements())[0] #We only want the "x" element (data form)
@ -63,47 +63,47 @@ class XEP_0077():
#TODO: manage registration without data form
answer_data={"reason": "unmanaged", "message":_("This gateway can't be managed by SàT, sorry :(")}
answer_type = "ERROR"
self.host.bridge.actionResult(answer_type, answer['id'], answer_data)
self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
return
form = data_form.Form.fromElement(x_elem)
xml_data = dataForm2xml(form)
self.host.bridge.actionResult("XMLUI", answer['id'], {"target":answer["from"], "type":"registration", "xml":xml_data})
self.host.bridge.actionResult("XMLUI", answer['id'], {"target":answer["from"], "type":"registration", "xml":xml_data}, profile)
def reg_err(self, failure):
def reg_err(self, failure, profile):
"""Called when something is wrong with registration"""
info (_("Registration failure: %s") % str(failure.value))
answer_data = {}
answer_data['reason'] = 'unknown'
answer_data={"message":"%s [code: %s]" % (failure.value.condition, unicode(failure.value))}
answer_type = "ERROR"
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data)
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
def unregistrationAnswer(self, answer):
def unregistrationAnswer(self, answer, profile):
debug (_("registration answer: %s") % answer.toXml())
answer_type = "SUCCESS"
answer_data={"message":_("Your are now unregistred")}
self.host.bridge.actionResult(answer_type, answer['id'], answer_data)
self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
def unregistrationFailure(self, failure):
def unregistrationFailure(self, failure, profile):
info (_("Unregistration failure: %s") % str(failure.value))
answer_type = "ERROR"
answer_data = {}
answer_data['reason'] = 'unknown'
answer_data={"message":_("Unregistration failed: %s") % failure.value.condition}
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data)
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
def registrationAnswer(self, answer):
def registrationAnswer(self, answer, profile):
debug (_("registration answer: %s") % answer.toXml())
answer_type = "SUCCESS"
answer_data={"message":_("Registration successfull")}
self.host.bridge.actionResult(answer_type, answer['id'], answer_data)
self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
if self.triggers.has_key(answer["from"]):
callback,profile = self.triggers[answer["from"]]
callback(answer["from"], profile)
del self.triggers[answer["from"]]
def registrationFailure(self, failure):
def registrationFailure(self, failure, profile):
info (_("Registration failure: %s") % str(failure.value))
print failure.value.stanza.toXml()
answer_type = "ERROR"
@ -114,7 +114,7 @@ class XEP_0077():
else:
answer_data['reason'] = 'unknown'
answer_data={"message":_("Registration failed")}
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data)
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
if self.triggers.has_key(answer["from"]):
del self.triggers[answer["from"]]
@ -122,22 +122,22 @@ class XEP_0077():
"""Submit a form for registration, using data_form"""
id, deferred = self.host.submitForm(action, target, fields, profile)
if action == 'CANCEL':
deferred.addCallbacks(self.unregistrationAnswer, self.unregistrationFailure)
deferred.addCallbacks(self.unregistrationAnswer, self.unregistrationFailure, callbackArgs=[profile], errbackArgs=[profile])
else:
deferred.addCallbacks(self.registrationAnswer, self.registrationFailure)
deferred.addCallbacks(self.registrationAnswer, self.registrationFailure, callbackArgs=[profile], errbackArgs=[profile])
return id
def in_band_register(self, target, profile_key='@DEFAULT@'):
"""register to a target JID"""
current_jid, xmlstream = self.host.getJidNStream(profile_key)
if not xmlstream:
client = self.host.getClient(profile_key)
if not client:
error (_('Asking for an non-existant or not connected profile'))
return ""
to_jid = jid.JID(target)
debug(_("Asking registration for [%s]") % to_jid.full())
reg_request=IQ(xmlstream,'get')
reg_request["from"]=current_jid.full()
reg_request=IQ(client.xmlstream,'get')
reg_request["from"]=client.jid.full()
reg_request["to"] = to_jid.full()
query=reg_request.addElement('query', NS_REG)
reg_request.send(to_jid.full()).addCallbacks(self.reg_ok, self.reg_err)
reg_request.send(to_jid.full()).addCallbacks(self.reg_ok, self.reg_err, callbackArgs=[profile], errbackArgs=[profile])
return reg_request["id"]

View file

@ -21,16 +21,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from logging import debug, info, warning, error
from twisted.words.xish import domish
from twisted.internet import protocol
from twisted.words.protocols.jabber import client, jid
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber import error as jab_error
import os, os.path
from twisted.internet import reactor
import pdb
from sat.core.exceptions import ProfileNotInCacheError
from zope.interface import implements
from wokkel import disco, iwokkel, data_form
from wokkel import data_form
IQ_SET = '/iq[@type="set"]'
PROFILE_NAME = "file-transfer"
@ -52,19 +50,23 @@ class XEP_0096():
def __init__(self, host):
info(_("Plugin XEP_0096 initialization"))
self.host = host
self._waiting_for_approval = {} #key = id, value = [transfer data, IdelayedCall Reactor timeout,
# current stream method, [failed stream methods], profile]
self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE,
self.managed_stream_m = [#self.host.plugins["XEP-0065"].NAMESPACE,
self.host.plugins["XEP-0047"].NAMESPACE] #Stream methods managed
self.host.plugins["XEP-0095"].registerSIProfile(PROFILE_NAME, self.transferRequest)
host.bridge.addMethod("sendFile", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self.sendFile)
def _kill_id(self, approval_id):
def profileConnected(self, profile):
client = self.host.getClient(profile)
client._xep_0096_waiting_for_approval = {} #key = id, value = [transfer data, IdelayedCall Reactor timeout,
# current stream method, [failed stream methods], profile]
def _kill_id(self, approval_id, profile):
"""Delete a waiting_for_approval id, called after timeout
@param approval_id: id of _waiting_for_approval"""
@param approval_id: id of _xep_0096_waiting_for_approval"""
info(_("SI File Transfer: TimeOut reached for id %s") % approval_id);
try:
del self._waiting_for_approval[approval_id]
client = self.host.getClient(profile)
del client._xep_0096_waiting_for_approval[approval_id]
except KeyError:
warning(_("kill id called on a non existant approval id"))
@ -78,6 +80,9 @@ class XEP_0096():
@param profile: %(doc_profile)s"""
info (_("XEP-0096 file transfer requested"))
debug(si_el.toXml())
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
filename = ""
file_size = ""
file_date = None
@ -124,9 +129,9 @@ class XEP_0096():
#if we are here, the transfer can start, we just need user's agreement
data={ "filename":filename, "id": iq_id, "from":from_jid, "size":file_size, "date":file_date, "hash":file_hash, "desc":file_desc, "can_range": str(can_range) }
self._waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id), stream_method, [], profile]
client._xep_0096_waiting_for_approval[si_id] = [data, reactor.callLater(300, self._kill_id, si_id, profile), stream_method, []]
self.host.askConfirmation(si_id, "FILE_TRANSFER", data, self.confirmationCB)
self.host.askConfirmation(si_id, "FILE_TRANSFER", data, self.confirmationCB, profile)
def _getFileObject(self, dest_path, can_range = False):
@ -136,12 +141,15 @@ class XEP_0096():
@return: File Object"""
return open(dest_path, "ab" if can_range else "wb")
def confirmationCB(self, sid, accepted, frontend_data):
def confirmationCB(self, sid, accepted, frontend_data, profile):
"""Called on confirmation answer
@param sid: file transfer session id
@param accepted: True if file transfer is accepted
@param frontend_data: data sent by frontend"""
data, timeout, stream_method, failed_methods, profile = self._waiting_for_approval[sid]
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
can_range = data['can_range'] == "True"
range_offset = 0
if accepted:
@ -151,19 +159,19 @@ class XEP_0096():
dest_path = frontend_data['dest_path']
except KeyError:
error(_('dest path not found in frontend_data'))
del(self._waiting_for_approval[sid])
del(client._xep_0096_waiting_for_approval[sid])
return
if stream_method == self.host.plugins["XEP-0065"].NAMESPACE:
file_obj = self._getFileObject(dest_path, can_range)
range_offset = file_obj.tell()
self.host.plugins["XEP-0065"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed)
self.host.plugins["XEP-0065"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed, profile)
elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
file_obj = self._getFileObject(dest_path, can_range)
range_offset = file_obj.tell()
self.host.plugins["XEP-0047"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed)
self.host.plugins["XEP-0047"].prepareToReceive(jid.JID(data['from']), sid, file_obj, int(data["size"]), self._transferSucceeded, self._transferFailed, profile)
else:
error(_("Unknown stream method, this should not happen at this stage, cancelling transfer"))
del(self._waiting_for_approval[sid])
del(client._xep_0096_waiting_for_approval[sid])
return
#we can send the iq result
@ -179,30 +187,37 @@ class XEP_0096():
else:
debug (_("Transfer [%s] refused"), sid)
self.host.plugins["XEP-0095"].sendRejectedError (data["id"], data['from'], profile=profile)
del(self._waiting_for_approval[sid])
del(client._xep_0096_waiting_for_approval[sid])
def _transferSucceeded(self, sid, file_obj, stream_method):
def _transferSucceeded(self, sid, file_obj, stream_method, profile):
"""Called by the stream method when transfer successfuly finished
@param id: stream id"""
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
file_obj.close()
info(_('Transfer %s successfuly finished') % sid)
del(self._waiting_for_approval[sid])
del(client._xep_0096_waiting_for_approval[sid])
def _transferFailed(self, sid, file_obj, stream_method, reason):
def _transferFailed(self, sid, file_obj, stream_method, reason, profile):
"""Called when something went wrong with the transfer
@param id: stream id
@param reason: can be TIMEOUT, IO_ERROR, PROTOCOL_ERROR"""
data, timeout, stream_method, failed_methods, profile = self._waiting_for_approval[sid]
warning(_('Transfer %(id)s failed with stream method %(s_method)s') % { 'id': sid,
's_method': stream_method })
client = self.host.getClient(profile)
if not client:
raise ProfileNotInCacheError
data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
warning(_('Transfer %(id)s failed with stream method %(s_method)s: %(reason)s') % { 'id': sid,
's_method': stream_method,
'reason': reason})
filepath = file_obj.name
file_obj.close()
os.remove(filepath)
#TODO: session remenber (within a time limit) when a stream method fail, and avoid that stream method with full jid for the rest of the session
warning(_("All stream methods failed, can't transfer the file"))
del(self._waiting_for_approval[sid])
del(client._xep_0096_waiting_for_approval[sid])
def fileCb(self, profile, filepath, sid, size, IQ):
def fileCb(self, filepath, sid, size, profile, IQ):
if IQ['type'] == "error":
stanza_err = jab_error.exceptionFromStanza(IQ)
if stanza_err.code == '403' and stanza_err.condition == 'forbidden':
@ -252,7 +267,7 @@ class XEP_0096():
else:
warning(_("Invalid stream method received"))
def sendFile(self, to_jid, filepath, data={}, profile_key='@DEFAULT@'):
def sendFile(self, to_jid, filepath, data={}, profile_key='@NONE@'):
"""send a file using XEP-0096
@to_jid: recipient
@filepath: absolute path to the file to send
@ -278,13 +293,13 @@ class XEP_0096():
file_transfer_elts.append(domish.Element((None,'range')))
sid, offer = self.host.plugins["XEP-0095"].proposeStream(jid.JID(to_jid), PROFILE, feature_elt, file_transfer_elts, profile_key = profile)
offer.addCallback(self.fileCb, profile, filepath, sid, size)
offer.addCallback(self.fileCb, filepath, sid, size, profile)
return sid
def sendSuccessCb(self, sid, file_obj, stream_method):
info(_('Transfer %s successfuly finished') % sid)
def sendSuccessCb(self, sid, file_obj, stream_method, profile):
info(_('Transfer %s successfuly finished [%s]') % (sid, profile))
file_obj.close()
def sendFailureCb(self, sid, file_obj, stream_method, reason):
def sendFailureCb(self, sid, file_obj, stream_method, reason, profile):
file_obj.close()
warning(_('Transfer %(id)s failed with stream method %(s_method)s') % { 'id': sid, "s_method": stream_method })
warning(_('Transfer %(id)s failed with stream method %(s_method)s: %(reason)s [%(profile)s') % { 'id': sid, "s_method": stream_method, 'reason': reason, 'profile': profile })