diff --git a/sylk/applications/xmppgateway/datatypes.py b/sylk/applications/xmppgateway/datatypes.py index e3191ff..7715a1f 100644 --- a/sylk/applications/xmppgateway/datatypes.py +++ b/sylk/applications/xmppgateway/datatypes.py @@ -1,166 +1,155 @@ # Copyright (C) 2012 AG Projects. See LICENSE for details # import hashlib import random import string from application.python.descriptor import WriteOnceAttribute from sipsimple.core import BaseSIPURI, SIPURI, SIPCoreError from twisted.words.protocols.jabber.jid import JID sylkserver_prefix = hashlib.md5('sylkserver').hexdigest() def generate_sylk_resource(): r = 'sylk-'+''.join(random.choice(string.ascii_letters+string.digits) for x in range(32)) return r.encode('hex') def is_sylk_resource(r): if r.startswith('urn:uuid:') or len(r) != 74: return False try: decoded = r.decode('hex') except TypeError: return False else: return decoded.startswith('sylk-') def encode_resource(r): return r.encode('utf-8').encode('hex') def decode_resource(r): return r.decode('hex').decode('utf-8') class BaseURI(object): def __init__(self, user, host, resource=None): self.user = user self.host = host self.resource = resource @classmethod def parse(cls, value): if isinstance(value, BaseSIPURI): user = unicode(value.user) host = unicode(value.host) resource = unicode(value.parameters.get('gr', '')) or None return cls(user, host, resource) elif isinstance(value, JID): user = value.user host = value.host resource = value.resource return cls(user, host, resource) elif not isinstance(value, basestring): raise TypeError('uri needs to be a string') if not value.startswith(('sip:', 'sips:', 'xmpp:')): raise ValueError('invalid uri scheme for %s' % value) if value.startswith(('sip:', 'sips:')): try: uri = SIPURI.parse(value) except SIPCoreError: raise ValueError('invalid SIP uri: %s' % value) user = unicode(uri.user) host = unicode(uri.host) resource = unicode(uri.parameters.get('gr', '')) or None else: try: jid = JID(value[5:]) except Exception: raise ValueError('invalid XMPP uri: %s' % value) user = jid.user host = jid.host resource = jid.resource return cls(user, host, resource) @classmethod def new(cls, uri): if not isinstance(uri, BaseURI): raise TypeError('%s is not a valid URI type' % type(uri)) return cls(uri.user, uri.host, uri.resource) def as_sip_uri(self): uri = SIPURI(user=str(self.user), host=str(self.host)) if self.resource is not None: uri.parameters['gr'] = self.resource.encode('utf-8') return uri def as_xmpp_jid(self): - jid = JID(tuple=(self.user, self.host, self.resource)) - return jid - - def as_string(self, protocol): - if protocol not in ('sip', 'xmpp'): - raise ValueError('protocol must be one of "sip" or "xmpp"') - if protocol == 'sip': - uri = self.as_sip_uri() - return unicode(str(uri)) - else: - uri = self.as_xmpp_jid() - return unicode(uri) + return JID(tuple=(self.user, self.host, self.resource)) def __eq__(self, other): if isinstance(other, BaseURI): return self.user == other.user and self.host == other.host and self.resource == other.resource elif isinstance(other, basestring): try: other = BaseURI.parse(other) except ValueError: return False else: return self.user == other.user and self.host == other.host and self.resource == other.resource else: return NotImplemented def __ne__(self, other): equal = self.__eq__(other) return NotImplemented if equal is NotImplemented else not equal def __repr__(self): return '%s(user=%r, host=%r, resource=%r)' % (self.__class__.__name__, self.user, self.host, self.resource) def __unicode__(self): return u'%s@%s' % (self.user, self.host) def __str__(self): return unicode(self).encode('utf-8') class URI(BaseURI): pass class FrozenURI(BaseURI): user = WriteOnceAttribute() host = WriteOnceAttribute() resource = WriteOnceAttribute() def __hash__(self): return hash((self.user, self.host, self.resource)) class Identity(object): def __init__(self, uri, display_name=None): self.uri = uri self.display_name = display_name def __eq__(self, other): if isinstance(other, Identity): return self.uri == other.uri and self.display_name == other.display_name else: return NotImplemented def __ne__(self, other): equal = self.__eq__(other) return NotImplemented if equal is NotImplemented else not equal def __unicode__(self): if self.display_name is not None: return u'%s <%s>' % (self.display_name, self.uri) else: return u'%s' % self.uri def __str__(self): return unicode(self).encode('utf-8') diff --git a/sylk/applications/xmppgateway/xmpp/stanzas/__init__.py b/sylk/applications/xmppgateway/xmpp/stanzas/__init__.py index 4fcd8b3..9d1b0db 100644 --- a/sylk/applications/xmppgateway/xmpp/stanzas/__init__.py +++ b/sylk/applications/xmppgateway/xmpp/stanzas/__init__.py @@ -1,281 +1,281 @@ # Copyright (C) 2012 AG Projects. See LICENSE for details # from twisted.words.xish import domish from sylk import __version__ as SYLK_VERSION from sylk.applications.xmppgateway.util import html2text CHATSTATES_NS = 'http://jabber.org/protocol/chatstates' RECEIPTS_NS = 'urn:xmpp:receipts' STANZAS_NS = 'urn:ietf:params:xml:ns:xmpp-stanzas' XML_NS = 'http://www.w3.org/XML/1998/namespace' MUC_NS = 'http://jabber.org/protocol/muc' MUC_USER_NS = MUC_NS + '#user' CAPS_NS = 'http://jabber.org/protocol/caps' SYLK_CAPS = [] class BaseStanza(object): stanza_type = None # to be defined by subclasses type = None def __init__(self, sender, recipient, id=None): self.sender = sender self.recipient = recipient self.id = id def to_xml_element(self): xml_element = domish.Element((None, self.stanza_type)) - xml_element['from'] = self.sender.uri.as_string('xmpp') - xml_element['to'] = self.recipient.uri.as_string('xmpp') + xml_element['from'] = unicode(self.sender.uri.as_xmpp_jid()) + xml_element['to'] = unicode(self.recipient.uri.as_xmpp_jid()) if self.type: xml_element['type'] = self.type if self.id is not None: xml_element['id'] = self.id return xml_element class ErrorStanza(object): """ Stanza representing an error of another stanza. It's not a base stanza type on its own. """ def __init__(self, stanza_type, sender, recipient, error_type, conditions, id=None): self.stanza_type = stanza_type self.sender = sender self.recipient = recipient self.id = id self.conditions = conditions self.error_type = error_type @classmethod def from_stanza(cls, stanza, error_type, conditions): # In error stanzas sender and recipient are swapped return cls(stanza.stanza_type, stanza.recipient, stanza.sender, error_type, conditions, id=stanza.id) def to_xml_element(self): xml_element = domish.Element((None, self.stanza_type)) - xml_element['from'] = self.sender.uri.as_string('xmpp') - xml_element['to'] = self.recipient.uri.as_string('xmpp') + xml_element['from'] = unicode(self.sender.uri.as_xmpp_jid()) + xml_element['to'] = unicode(self.recipient.uri.as_xmpp_jid()) xml_element['type'] = 'error' if self.id is not None: xml_element['id'] = self.id error_element = domish.Element((None, 'error')) error_element['type'] = self.error_type [error_element.addChild(domish.Element((ns, condition))) for condition, ns in self.conditions] xml_element.addChild(error_element) return xml_element class BaseMessageStanza(BaseStanza): stanza_type = 'message' def __init__(self, sender, recipient, body=None, html_body=None, id=None, use_receipt=False): super(BaseMessageStanza, self).__init__(sender, recipient, id=id) self.use_receipt = use_receipt if body is not None and html_body is None: self.body = body self.html_body = None elif body is None and html_body is not None: self.body = html2text(html_body) self.html_body = html_body else: self.body = body self.html_body = html_body def to_xml_element(self): xml_element = super(BaseMessageStanza, self).to_xml_element() if self.id is not None and self.recipient.uri.resource is not None and self.use_receipt: xml_element.addElement('request', defaultUri=RECEIPTS_NS) if self.body is not None: xml_element.addElement('body', content=self.body) if self.html_body is not None: xml_element.addElement('html', content=self.html_body) return xml_element class NormalMessage(BaseMessageStanza): def __init__(self, sender, recipient, body=None, html_body=None, id=None, use_receipt=False): if body is None and html_body is None: raise ValueError('either body or html_body need to be set') super(NormalMessage, self).__init__(sender, recipient, body, html_body, id, use_receipt) class ChatMessage(BaseMessageStanza): type = 'chat' def __init__(self, sender, recipient, body=None, html_body=None, id=None, use_receipt=True): if body is None and html_body is None: raise ValueError('either body or html_body need to be set') super(ChatMessage, self).__init__(sender, recipient, body, html_body, id, use_receipt) def to_xml_element(self): xml_element = super(ChatMessage, self).to_xml_element() xml_element.addElement('active', defaultUri=CHATSTATES_NS) return xml_element class ChatComposingIndication(BaseMessageStanza): type = 'chat' def __init__(self, sender, recipient, state, id=None, use_receipt=False): super(ChatComposingIndication, self).__init__(sender, recipient, id=id, use_receipt=use_receipt) self.state = state def to_xml_element(self): xml_element = super(ChatComposingIndication, self).to_xml_element() xml_element.addElement(self.state, defaultUri=CHATSTATES_NS) return xml_element class GroupChatMessage(BaseMessageStanza): type = 'groupchat' def __init__(self, sender, recipient, body=None, html_body=None, id=None): # TODO: add timestamp if body is None and html_body is None: raise ValueError('either body or html_body need to be set') super(GroupChatMessage, self).__init__(sender, recipient, body, html_body, id, False) class MessageReceipt(BaseMessageStanza): def __init__(self, sender, recipient, receipt_id, id=None): super(MessageReceipt, self).__init__(sender, recipient, id=id, use_receipt=False) self.receipt_id = receipt_id def to_xml_element(self): xml_element = super(MessageReceipt, self).to_xml_element() receipt_element = domish.Element((RECEIPTS_NS, 'received')) receipt_element['id'] = self.receipt_id xml_element.addChild(receipt_element) return xml_element class BasePresenceStanza(BaseStanza): stanza_type = 'presence' class IncomingInvitationMessage(BaseMessageStanza): def __init__(self, sender, recipient, invited_user, reason=None, id=None): super(IncomingInvitationMessage, self).__init__(sender, recipient, body=None, html_body=None, id=id, use_receipt=False) self.invited_user = invited_user self.reason = reason def to_xml_element(self): xml_element = super(IncomingInvitationMessage, self).to_xml_element() child = xml_element.addElement((MUC_USER_NS, 'x')) child.addElement('invite') - child.invite['to'] = self.invited_user.uri.as_string('xmpp') + child.invite['to'] = unicode(self.invited_user.uri.as_xmpp_jid()) if self.reason: child.invite.addElement('reason', content=self.reason) return xml_element class OutgoingInvitationMessage(BaseMessageStanza): def __init__(self, sender, recipient, originator, reason=None, id=None): super(OutgoingInvitationMessage, self).__init__(sender, recipient, body=None, html_body=None, id=id, use_receipt=False) self.originator = originator self.reason = reason def to_xml_element(self): xml_element = super(OutgoingInvitationMessage, self).to_xml_element() child = xml_element.addElement((MUC_USER_NS, 'x')) child.addElement('invite') - child.invite['from'] = self.originator.uri.as_string('xmpp') + child.invite['from'] = unicode(self.originator.uri.as_xmpp_jid()) if self.reason: child.invite.addElement('reason', content=self.reason) return xml_element class AvailabilityPresence(BasePresenceStanza): def __init__(self, sender, recipient, available=True, show=None, statuses=None, priority=0, id=None): super(AvailabilityPresence, self).__init__(sender, recipient, id=id) self.available = available self.show = show self.priority = priority self.statuses = statuses or {} def _get_available(self): return self.__dict__['available'] def _set_available(self, available): if available: self.type = None else: self.type = 'unavailable' self.__dict__['available'] = available available = property(_get_available, _set_available) del _get_available, _set_available @property def status(self): status = self.statuses.get(None) if status is None: try: status = self.statuses.itervalues().next() except StopIteration: pass return status def to_xml_element(self): xml_element = super(BasePresenceStanza, self).to_xml_element() if self.available: if self.show is not None: xml_element.addElement('show', content=self.show) if self.priority != 0: xml_element.addElement('priority', content=unicode(self.priority)) caps = xml_element.addElement('c', defaultUri=CAPS_NS) caps['node'] = 'http://sylkserver.com' caps['ver'] = SYLK_VERSION if SYLK_CAPS: caps['ext'] = ' '.join(SYLK_CAPS) for lang, text in self.statuses.iteritems(): status = xml_element.addElement('status', content=text) if lang: status[(XML_NS, 'lang')] = lang return xml_element class SubscriptionPresence(BasePresenceStanza): def __init__(self, sender, recipient, type, id=None): super(SubscriptionPresence, self).__init__(sender, recipient, id=id) self.type = type class ProbePresence(BasePresenceStanza): type = 'probe' class MUCAvailabilityPresence(AvailabilityPresence): def __init__(self, sender, recipient, available=True, show=None, statuses=None, priority=0, id=None, affiliation=None, jid=None, role=None, muc_statuses=None): super(MUCAvailabilityPresence, self).__init__(sender, recipient, available, show, statuses, priority, id) self.affiliation = affiliation or 'member' self.role = role or 'participant' self.muc_statuses = muc_statuses or [] self.jid = jid def to_xml_element(self): xml_element = super(MUCAvailabilityPresence, self).to_xml_element() muc = xml_element.addElement('x', defaultUri=MUC_USER_NS) item = muc.addElement('item') if self.affiliation: item['affiliation'] = self.affiliation if self.role: item['role'] = self.role if self.jid: - item['jid'] = self.jid.uri.as_string('xmpp') + item['jid'] = unicode(self.jid.uri.as_xmpp_jid()) for code in self.muc_statuses: status = muc.addElement('status') status['code'] = code return xml_element class MUCErrorPresence(ErrorStanza): def to_xml_element(self): xml_element = super(MUCErrorPresence, self).to_xml_element() xml_element.addElement('x', defaultUri=MUC_USER_NS) return xml_element