Compare commits

..

1 Commits

Author SHA1 Message Date
Lance Stout
846f2bac5f Bump version to 1.1.11 2012-10-31 13:54:38 -07:00
198 changed files with 2136 additions and 9866 deletions

7
.gitignore vendored
View File

@@ -1,4 +1,4 @@
*.py[co] *.pyc
build/ build/
dist/ dist/
MANIFEST MANIFEST
@@ -7,8 +7,3 @@ docs/_build/
.tox/ .tox/
.coverage .coverage
sleekxmpp.egg-info/ sleekxmpp.egg-info/
.ropeproject/
4913
*~
.baboon/
.DS_STORE

31
LICENSE
View File

@@ -69,8 +69,8 @@ modification, are permitted provided that the following conditions are met:
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of Red Innovation nor the names of its contributors * Neither the name of Red Innovation nor the names of its contributors
may be used to endorse or promote products derived from this software may be used to endorse or promote products derived from this software
without specific prior written permission. without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
@@ -119,7 +119,7 @@ SUELTA A PURE-PYTHON SASL CLIENT LIBRARY
This software is subject to "The MIT License" This software is subject to "The MIT License"
Copyright 2004-2013 David Alan Cridland Copyright 2007-2010 David Alan Cridland
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
socksipy: A Python SOCKS client module.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.

View File

@@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github
``develop`` branch. ``develop`` branch.
**Latest Release** **Latest Release**
- `1.3.0 <http://github.com/fritzy/SleekXMPP/zipball/1.3.0>`_ - `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_
**Develop Releases** **Develop Releases**
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_

View File

@@ -222,7 +222,7 @@ handler function to process registration requests.
self.description = "In-Band Registration" self.description = "In-Band Registration"
self.xep = "0077" self.xep = "0077"
self.xmpp.register_handler( self.xmpp.registerHandler(
Callback('In-Band Registration', Callback('In-Band Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
self.__handleRegistration)) self.__handleRegistration))
@@ -601,7 +601,7 @@ with some additional registration fields implemented.
self.form_instructions = "" self.form_instructions = ""
self.backend = UserStore() self.backend = UserStore()
self.xmpp.register_handler( self.xmpp.registerHandler(
Callback('In-Band Registration', Callback('In-Band Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
self.__handleRegistration)) self.__handleRegistration))

View File

@@ -6,20 +6,14 @@ Event Index
connected connected
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` - **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
Signal that a connection has been made with the XMPP server, but a session Signal that a connection has been made with the XMPP server, but a session
has not yet been established. has not yet been established.
connection_failed
- **Data:** ``{}`` or ``Failure Stanza`` if available
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
Signal that a connection can not be established after number of attempts.
changed_status changed_status
- **Data:** :py:class:`~sleekxmpp.Presence` - **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem` - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
Triggered when a presence stanza is received from a JID with a show type Triggered when a presence stanza is received from a JID with a show type
different than the last presence stanza from the same JID. different than the last presence stanza from the same JID.
@@ -71,8 +65,8 @@ Event Index
disconnected disconnected
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` - **Source:** :py:class:`~sleekxmpp.ClientXMPP`
Signal that the connection with the XMPP server has been lost. Signal that the connection with the XMPP server has been lost.
entity_time entity_time
@@ -99,16 +93,16 @@ Event Index
got_online got_online
- **Data:** :py:class:`~sleekxmpp.Presence` - **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem` - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
If a presence stanza is received from a JID which was previously marked as If a presence stanza is received from a JID which was previously marked as
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``', offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
or '``xa``', then this event is triggered as well. or '``xa``', then this event is triggered as well.
got_offline got_offline
- **Data:** :py:class:`~sleekxmpp.Presence` - **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem` - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
Signal that an unavailable presence stanza has been received from a JID. Signal that an unavailable presence stanza has been received from a JID.
groupchat_invite groupchat_invite
@@ -116,7 +110,7 @@ Event Index
- **Source:** - **Source:**
groupchat_direct_invite groupchat_direct_invite
- **Data:** :py:class:`~sleekxmpp.Message` - **Data:** :py:class:`~sleekxmpp.Message`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct` - **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
groupchat_message groupchat_message
@@ -153,18 +147,18 @@ Event Index
sure to check the message type in order to handle error messages. sure to check the message type in order to handle error messages.
message_form message_form
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
Currently the same as :term:`message_xform`. Currently the same as :term:`message_xform`.
message_xform message_xform
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
Triggered whenever a data form is received inside a message. Triggered whenever a data form is received inside a message.
muc::[room]::got_offline mucc::[room]::got_offline
- **Data:** - **Data:**
- **Source:** - **Source:**
@@ -193,8 +187,8 @@ Event Index
A presence stanza with a type of '``error``' is received. A presence stanza with a type of '``error``' is received.
presence_form presence_form
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
This event is present in the XEP-0004 plugin code, but is currently not used. This event is present in the XEP-0004 plugin code, but is currently not used.
@@ -235,20 +229,22 @@ Event Index
A presence stanza with a type of '``unsubscribed``' is received. A presence stanza with a type of '``unsubscribed``' is received.
roster_update roster_update
- **Data:** :py:class:`~sleekxmpp.stanza.Roster` - **Data:** :py:class:`~sleekxmpp.stanza.Roster`
- **Source:** :py:class:`~sleekxmpp.ClientXMPP` - **Source:** :py:class:`~sleekxmpp.ClientXMPP`
An IQ result containing roster entries is received. An IQ result containing roster entries is received.
sent_presence sent_presence
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.roster.multi.Roster` - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
Signal that an initial presence stanza has been written to the XML stream. Signal that an initial presence stanza has been written to the XML stream.
session_end session_end
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
Signal that a connection to the XMPP server has been lost and the current Signal that a connection to the XMPP server has been lost and the current
stream session has ended. Currently equivalent to :term:`disconnected`, but stream session has ended. Currently equivalent to :term:`disconnected`, but
@@ -260,14 +256,14 @@ Event Index
session_start session_start
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`, - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>` :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>` :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
Signal that a connection to the XMPP server has been made and a session has been established. Signal that a connection to the XMPP server has been made and a session has been established.
socket_error socket_error
- **Data:** ``Socket`` exception object - **Data:** ``Socket`` exception object
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
stream_error stream_error

View File

@@ -1,203 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Implementation of xeps for Internet of Things
http://wiki.xmpp.org/web/Tech_pages/IoT_systems
Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import os
import sys
# This can be used when you are in a test environment and need to make paths right
sys.path=['/Users/jocke/Dropbox/06_dev/SleekXMPP']+sys.path
import logging
import unittest
import distutils.core
import datetime
from glob import glob
from os.path import splitext, basename, join as pjoin
from optparse import OptionParser
from urllib import urlopen
import sleekxmpp
# Python versions before 3.0 do not use UTF-8 encoding
# by default. To ensure that Unicode is handled properly
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
from sleekxmpp.plugins.xep_0323.device import Device
#from sleekxmpp.exceptions import IqError, IqTimeout
class IoT_TestDevice(sleekxmpp.ClientXMPP):
"""
A simple IoT device that can act as server or client
"""
def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.add_event_handler("session_start", self.session_start)
self.add_event_handler("message", self.message)
self.device=None
self.releaseMe=False
self.beServer=True
self.clientJID=None
def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None):
"""
This method will be called when you ask another IoT device for data with the xep_0323
se script below for the registration of the callback
"""
logging.debug("we got data %s from %s",str(result),from_jid)
def beClientOrServer(self,server=True,clientJID=None ):
if server:
self.beServer=True
self.clientJID=None
else:
self.beServer=False
self.clientJID=clientJID
def testForRelease(self):
# todo thread safe
return self.releaseMe
def doReleaseMe(self):
# todo thread safe
self.releaseMe=True
def addDevice(self, device):
self.device=device
def session_start(self, event):
self.send_presence()
self.get_roster()
# tell your preffered friend that you are alive
self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me')
if not(self.beServer):
session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback)
def message(self, msg):
if msg['type'] in ('chat', 'normal'):
logging.debug("got normal chat message" + str(msg))
ip=urlopen('http://icanhazip.com').read()
msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send()
else:
logging.debug("got unknown message type %s", str(msg['type']))
class TheDevice(Device):
"""
This is the actual device object that you will use to get information from your real hardware
You will be called in the refresh method when someone is requesting information from you
"""
def __init__(self,nodeId):
Device.__init__(self,nodeId)
self.counter=0
def refresh(self,fields):
"""
the implementation of the refresh method
"""
self._set_momentary_timestamp(self._get_timestamp())
self.counter+=self.counter
self._add_field_momentary_data(self, "Temperature", self.counter)
if __name__ == '__main__':
# Setup the command line arguments.
#
# This script can act both as
# "server" an IoT device that can provide sensorinformation
# python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug
#
# "client" an IoT device or other party that would like to get data from another device
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
optp.add_option('-t', '--pingto', help='set jid to ping',
action='store', type='string', dest='pingjid',
default=None)
# JID and password options.
optp.add_option("-j", "--jid", dest="jid",
help="JID to use")
optp.add_option("-p", "--password", dest="password",
help="password to use")
# IoT test
optp.add_option("-c", "--sensorjid", dest="sensorjid",
help="Another device to call for data on", default=None)
optp.add_option("-n", "--nodeid", dest="nodeid",
help="I am a device get ready to be called", default=None)
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
xmpp = IoT_TestDevice(opts.jid,opts.password)
xmpp.register_plugin('xep_0030')
#xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata',
# node=None,
# jid=None)
xmpp.register_plugin('xep_0323')
xmpp.register_plugin('xep_0325')
if opts.nodeid:
# xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn',
# node=opts.nodeid,
# jid=xmpp.boundjid.full)
myDevice = TheDevice(opts.nodeid);
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10);
xmpp.beClientOrServer(server=True)
while not(xmpp.testForRelease()):
xmpp.connect()
xmpp.process(block=True)
logging.debug("lost connection")
if opts.sensorjid:
logging.debug("will try to call another device for data")
xmpp.beClientOrServer(server=False,clientJID=opts.sensorjid)
xmpp.connect()
xmpp.process(block=True)
logging.debug("ready ending")
else:
print "noopp didn't happen"

View File

@@ -51,7 +51,7 @@ class ActionBot(sleekxmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
self.register_handler( self.registerHandler(
Callback('Some custom iq', Callback('Some custom iq',
StanzaPath('iq@type=set/action'), StanzaPath('iq@type=set/action'),
self._handle_action)) self._handle_action))

0
examples/download_avatars.py Executable file → Normal file
View File

View File

@@ -38,7 +38,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0030') # Service Discovery self.register_plugin('xep_0030') # Service Discovery
self.register_plugin('xep_0047', { self.register_plugin('xep_0047', {
'auto_accept': True 'accept_stream': self.accept_stream
}) # In-band Bytestreams }) # In-band Bytestreams
# The session_start event will be triggered when # The session_start event will be triggered when
@@ -48,7 +48,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
self.add_event_handler("ibb_stream_start", self.stream_opened, threaded=True) self.add_event_handler("ibb_stream_start", self.stream_opened)
self.add_event_handler("ibb_stream_data", self.stream_data) self.add_event_handler("ibb_stream_data", self.stream_data)
def start(self, event): def start(self, event):
@@ -69,7 +69,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
def accept_stream(self, iq): def accept_stream(self, iq):
""" """
Check that it is ok to accept a stream request. Check that it is ok to accept a stream request.
Controlling stream acceptance can be done via either: Controlling stream acceptance can be done via either:
- setting 'auto_accept' to False in the plugin - setting 'auto_accept' to False in the plugin
@@ -83,7 +83,9 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
return True return True
def stream_opened(self, stream): def stream_opened(self, stream):
print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) # NOTE: IBB streams are bi-directional, so the original sender is
# now the opened stream's receiver.
print('Stream opened: %s from %s' % (stream.sid, stream.receiver))
# You could run a loop reading from the stream using stream.recv(), # You could run a loop reading from the stream using stream.recv(),
# or use the ibb_stream_data event. # or use the ibb_stream_data event.

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import logging
import getpass
from optparse import OptionParser
import sleekxmpp
# Python versions before 3.0 do not use UTF-8 encoding
# by default. To ensure that Unicode is handled properly
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
# Setup the command line arguments.
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
# JID and password options.
optp.add_option("--oldjid", dest="old_jid",
help="JID of the old account")
optp.add_option("--oldpassword", dest="old_password",
help="password of the old account")
optp.add_option("--newjid", dest="new_jid",
help="JID of the old account")
optp.add_option("--newpassword", dest="new_password",
help="password of the old account")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.old_jid is None:
opts.old_jid = raw_input("Old JID: ")
if opts.old_password is None:
opts.old_password = getpass.getpass("Old Password: ")
if opts.new_jid is None:
opts.new_jid = raw_input("New JID: ")
if opts.new_password is None:
opts.new_password = getpass.getpass("New Password: ")
old_xmpp = sleekxmpp.ClientXMPP(opts.old_jid, opts.old_password)
# If you are connecting to Facebook and wish to use the
# X-FACEBOOK-PLATFORM authentication mechanism, you will need
# your API key and an access token. Then you'll set:
# xmpp.credentials['api_key'] = 'THE_API_KEY'
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
# If you are connecting to MSN, then you will need an
# access token, and it does not matter what JID you
# specify other than that the domain is 'messenger.live.com',
# so '_@messenger.live.com' will work. You can specify
# the access token as so:
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
roster = []
def on_session(event):
roster.append(old_xmpp.get_roster())
old_xmpp.disconnect()
old_xmpp.add_event_handler('session_start', on_session)
if old_xmpp.connect():
old_xmpp.process(block=True)
if not roster:
print('No roster to migrate')
sys.exit()
new_xmpp = sleekxmpp.ClientXMPP(opts.new_jid, opts.new_password)
def on_session2(event):
new_xmpp.get_roster()
new_xmpp.send_presence()
logging.info(roster[0])
data = roster[0]['roster']['items']
logging.info(data)
for jid, item in data.items():
if item['subscription'] != 'none':
new_xmpp.send_presence(ptype='subscribe', pto=jid)
new_xmpp.update_roster(jid,
name = item['name'],
groups = item['groups'])
new_xmpp.disconnect()
new_xmpp.add_event_handler('session_start', on_session2)
if new_xmpp.connect():
new_xmpp.process(block=True)

View File

@@ -37,7 +37,7 @@ class PingTest(sleekxmpp.ClientXMPP):
def __init__(self, jid, password, pingjid): def __init__(self, jid, password, pingjid):
sleekxmpp.ClientXMPP.__init__(self, jid, password) sleekxmpp.ClientXMPP.__init__(self, jid, password)
if pingjid is None: if pingjid is None:
pingjid = self.boundjid.bare pingjid = self.jid
self.pingjid = pingjid self.pingjid = pingjid
# The session_start event will be triggered when # The session_start event will be triggered when
@@ -62,18 +62,16 @@ class PingTest(sleekxmpp.ClientXMPP):
""" """
self.send_presence() self.send_presence()
self.get_roster() self.get_roster()
result = self['xep_0199'].send_ping(self.pingjid,
try: timeout=10,
rtt = self['xep_0199'].ping(self.pingjid, errorfalse=True)
timeout=10) logging.info("Pinging...")
logging.info("Success! RTT: %s", rtt) if result is False:
except IqError as e: logging.info("Couldn't ping.")
logging.info("Error pinging %s: %s", self.disconnect()
self.pingjid, sys.exit(1)
e.iq['error']['condition']) else:
except IqTimeout: logging.info("Success! RTT: %s", str(result))
logging.info("No response from %s", self.pingjid)
finally:
self.disconnect() self.disconnect()

7
examples/pubsub_client.py Executable file → Normal file
View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys import sys
import logging import logging
import getpass import getpass
@@ -23,7 +20,7 @@ else:
class PubsubClient(sleekxmpp.ClientXMPP): class PubsubClient(sleekxmpp.ClientXMPP):
def __init__(self, jid, password, server, def __init__(self, jid, password, server,
node=None, action='list', data=''): node=None, action='list', data=''):
super(PubsubClient, self).__init__(jid, password) super(PubsubClient, self).__init__(jid, password)
@@ -31,7 +28,7 @@ class PubsubClient(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0059') self.register_plugin('xep_0059')
self.register_plugin('xep_0060') self.register_plugin('xep_0060')
self.actions = ['nodes', 'create', 'delete', self.actions = ['nodes', 'create', 'delete',
'publish', 'get', 'retract', 'publish', 'get', 'retract',
'purge', 'subscribe', 'unsubscribe'] 'purge', 'subscribe', 'unsubscribe']

5
examples/pubsub_events.py Executable file → Normal file
View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys import sys
import logging import logging
import getpass import getpass
@@ -80,7 +77,7 @@ class PubsubEvents(sleekxmpp.ClientXMPP):
"""Handle receiving a node deletion event.""" """Handle receiving a node deletion event."""
print('Deleted node %s' % ( print('Deleted node %s' % (
msg['pubsub_event']['delete']['node'])) msg['pubsub_event']['delete']['node']))
def _config(self, msg): def _config(self, msg):
"""Handle receiving a node configuration event.""" """Handle receiving a node configuration event."""
print('Configured node %s:' % ( print('Configured node %s:' % (

8
examples/register_account.py Executable file → Normal file
View File

@@ -51,7 +51,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
# The register event provides an Iq result stanza with # The register event provides an Iq result stanza with
# a registration form from the server. This may include # a registration form from the server. This may include
# the basic registration fields, a data form, an # the basic registration fields, a data form, an
# out-of-band URL, or any combination. For more advanced # out-of-band URL, or any combination. For more advanced
# cases, you will need to examine the fields provided # cases, you will need to examine the fields provided
# and respond accordingly. SleekXMPP provides plugins # and respond accordingly. SleekXMPP provides plugins
@@ -104,7 +104,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
resp.send(now=True) resp.send(now=True)
logging.info("Account created for %s!" % self.boundjid) logging.info("Account created for %s!" % self.boundjid)
except IqError as e: except IqError as e:
logging.error("Could not register account: %s" % logging.error("Could not register account: %s" %
e.iq['error']['text']) e.iq['error']['text'])
self.disconnect() self.disconnect()
except IqTimeout: except IqTimeout:
@@ -153,10 +153,6 @@ if __name__ == '__main__':
xmpp.register_plugin('xep_0066') # Out-of-band Data xmpp.register_plugin('xep_0066') # Out-of-band Data
xmpp.register_plugin('xep_0077') # In-band Registration xmpp.register_plugin('xep_0077') # In-band Registration
# Some servers don't advertise support for inband registration, even
# though they allow it. If this applies to your server, use:
xmpp['xep_0077'].force_registration = True
# If you are working with an OpenFire server, you may need # If you are working with an OpenFire server, you may need
# to adjust the SSL version used: # to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3 # xmpp.ssl_version = ssl.PROTOCOL_SSLv3

0
examples/roster_browser.py Executable file → Normal file
View File

29
examples/rpc_async.py Executable file → Normal file
View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens Copyright (C) 2011 Dann Martens
@@ -14,34 +11,34 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
import time import time
class Boomerang(Endpoint): class Boomerang(Endpoint):
def FQN(self): def FQN(self):
return 'boomerang' return 'boomerang'
@remote @remote
def throw(self): def throw(self):
print "Duck!" print "Duck!"
def main(): def main():
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****') session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
session.new_handler(ANY_ALL, Boomerang) session.new_handler(ANY_ALL, Boomerang)
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang) boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
callback = Future() callback = Future()
boomerang.async(callback).throw() boomerang.async(callback).throw()
time.sleep(10) time.sleep(10)
session.close() session.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

29
examples/rpc_client_side.py Executable file → Normal file
View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens Copyright (C) 2011 Dann Martens
@@ -15,18 +12,18 @@ import threading
import time import time
class Thermostat(Endpoint): class Thermostat(Endpoint):
def FQN(self): def FQN(self):
return 'thermostat' return 'thermostat'
def __init__(self, initial_temperature): def __init__(self, initial_temperature):
self._temperature = initial_temperature self._temperature = initial_temperature
self._event = threading.Event() self._event = threading.Event()
@remote @remote
def set_temperature(self, temperature): def set_temperature(self, temperature):
return NotImplemented return NotImplemented
@remote @remote
def get_temperature(self): def get_temperature(self):
return NotImplemented return NotImplemented
@@ -34,23 +31,23 @@ class Thermostat(Endpoint):
@remote(False) @remote(False)
def release(self): def release(self):
return NotImplemented return NotImplemented
def main(): def main():
session = Remote.new_session('operator@xmpp.org/rpc', '*****') session = Remote.new_session('operator@xmpp.org/rpc', '*****')
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat) thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
print("Current temperature is %s" % thermostat.get_temperature()) print("Current temperature is %s" % thermostat.get_temperature())
thermostat.set_temperature(20) thermostat.set_temperature(20)
time.sleep(10) time.sleep(10)
session.close() session.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

31
examples/rpc_server_side.py Executable file → Normal file
View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens Copyright (C) 2011 Dann Martens
@@ -14,42 +11,42 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
import threading import threading
class Thermostat(Endpoint): class Thermostat(Endpoint):
def FQN(self): def FQN(self):
return 'thermostat' return 'thermostat'
def __init__(self, initial_temperature): def __init__(self, initial_temperature):
self._temperature = initial_temperature self._temperature = initial_temperature
self._event = threading.Event() self._event = threading.Event()
@remote @remote
def set_temperature(self, temperature): def set_temperature(self, temperature):
print("Setting temperature to %s" % temperature) print("Setting temperature to %s" % temperature)
self._temperature = temperature self._temperature = temperature
@remote @remote
def get_temperature(self): def get_temperature(self):
return self._temperature return self._temperature
@remote(False) @remote(False)
def release(self): def release(self):
self._event.set() self._event.set()
def wait_for_release(self): def wait_for_release(self):
self._event.wait() self._event.wait()
def main(): def main():
session = Remote.new_session('sleek@xmpp.org/rpc', '*****') session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
thermostat = session.new_handler(ANY_ALL, Thermostat, 18) thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
thermostat.wait_for_release() thermostat.wait_for_release()
session.close() session.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

0
examples/set_avatar.py Executable file → Normal file
View File

0
examples/thirdparty_auth.py Executable file → Normal file
View File

0
examples/user_location.py Executable file → Normal file
View File

0
examples/user_tune.py Executable file → Normal file
View File

View File

@@ -42,7 +42,6 @@ CLASSIFIERS = [ 'Intended Audience :: Developers',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
] ]
@@ -63,22 +62,18 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0012', 'sleekxmpp/plugins/xep_0012',
'sleekxmpp/plugins/xep_0013', 'sleekxmpp/plugins/xep_0013',
'sleekxmpp/plugins/xep_0016', 'sleekxmpp/plugins/xep_0016',
'sleekxmpp/plugins/xep_0020',
'sleekxmpp/plugins/xep_0027', 'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030', 'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza', 'sleekxmpp/plugins/xep_0030/stanza',
'sleekxmpp/plugins/xep_0033', 'sleekxmpp/plugins/xep_0033',
'sleekxmpp/plugins/xep_0047', 'sleekxmpp/plugins/xep_0047',
'sleekxmpp/plugins/xep_0048',
'sleekxmpp/plugins/xep_0049', 'sleekxmpp/plugins/xep_0049',
'sleekxmpp/plugins/xep_0050', 'sleekxmpp/plugins/xep_0050',
'sleekxmpp/plugins/xep_0054', 'sleekxmpp/plugins/xep_0054',
'sleekxmpp/plugins/xep_0059', 'sleekxmpp/plugins/xep_0059',
'sleekxmpp/plugins/xep_0060', 'sleekxmpp/plugins/xep_0060',
'sleekxmpp/plugins/xep_0060/stanza', 'sleekxmpp/plugins/xep_0060/stanza',
'sleekxmpp/plugins/xep_0065',
'sleekxmpp/plugins/xep_0066', 'sleekxmpp/plugins/xep_0066',
'sleekxmpp/plugins/xep_0071',
'sleekxmpp/plugins/xep_0077', 'sleekxmpp/plugins/xep_0077',
'sleekxmpp/plugins/xep_0078', 'sleekxmpp/plugins/xep_0078',
'sleekxmpp/plugins/xep_0080', 'sleekxmpp/plugins/xep_0080',
@@ -87,21 +82,17 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0086', 'sleekxmpp/plugins/xep_0086',
'sleekxmpp/plugins/xep_0091', 'sleekxmpp/plugins/xep_0091',
'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0092',
'sleekxmpp/plugins/xep_0095',
'sleekxmpp/plugins/xep_0096',
'sleekxmpp/plugins/xep_0107', 'sleekxmpp/plugins/xep_0107',
'sleekxmpp/plugins/xep_0108', 'sleekxmpp/plugins/xep_0108',
'sleekxmpp/plugins/xep_0115', 'sleekxmpp/plugins/xep_0115',
'sleekxmpp/plugins/xep_0118', 'sleekxmpp/plugins/xep_0118',
'sleekxmpp/plugins/xep_0128', 'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0131', 'sleekxmpp/plugins/xep_0131',
'sleekxmpp/plugins/xep_0152',
'sleekxmpp/plugins/xep_0153', 'sleekxmpp/plugins/xep_0153',
'sleekxmpp/plugins/xep_0172', 'sleekxmpp/plugins/xep_0172',
'sleekxmpp/plugins/xep_0184', 'sleekxmpp/plugins/xep_0184',
'sleekxmpp/plugins/xep_0186', 'sleekxmpp/plugins/xep_0186',
'sleekxmpp/plugins/xep_0191', 'sleekxmpp/plugins/xep_0191',
'sleekxmpp/plugins/xep_0196',
'sleekxmpp/plugins/xep_0198', 'sleekxmpp/plugins/xep_0198',
'sleekxmpp/plugins/xep_0199', 'sleekxmpp/plugins/xep_0199',
'sleekxmpp/plugins/xep_0202', 'sleekxmpp/plugins/xep_0202',
@@ -118,16 +109,6 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0297', 'sleekxmpp/plugins/xep_0297',
'sleekxmpp/plugins/xep_0308', 'sleekxmpp/plugins/xep_0308',
'sleekxmpp/plugins/xep_0313', 'sleekxmpp/plugins/xep_0313',
'sleekxmpp/plugins/xep_0319',
'sleekxmpp/plugins/xep_0323',
'sleekxmpp/plugins/xep_0323/stanza',
'sleekxmpp/plugins/xep_0325',
'sleekxmpp/plugins/xep_0325/stanza',
'sleekxmpp/plugins/google',
'sleekxmpp/plugins/google/gmail',
'sleekxmpp/plugins/google/auth',
'sleekxmpp/plugins/google/settings',
'sleekxmpp/plugins/google/nosave',
'sleekxmpp/features', 'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms', 'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza', 'sleekxmpp/features/feature_mechanisms/stanza',

View File

@@ -6,25 +6,14 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import logging
if hasattr(logging, 'NullHandler'):
NullHandler = logging.NullHandler
else:
class NullHandler(logging.Handler):
def handle(self, record):
pass
logging.getLogger(__name__).addHandler(NullHandler())
del NullHandler
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.jid import JID, InvalidJID
from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.basexmpp import BaseXMPP from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.clientxmpp import ClientXMPP from sleekxmpp.clientxmpp import ClientXMPP
from sleekxmpp.componentxmpp import ComponentXMPP from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.jid import JID, InvalidJID
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
from sleekxmpp.version import __version__, __version_info__ from sleekxmpp.version import __version__, __version_info__

View File

@@ -18,7 +18,8 @@ import sys
import logging import logging
import threading import threading
from sleekxmpp import plugins, roster, stanza import sleekxmpp
from sleekxmpp import plugins, features, roster
from sleekxmpp.api import APIRegistry from sleekxmpp.api import APIRegistry
from sleekxmpp.exceptions import IqError, IqTimeout from sleekxmpp.exceptions import IqError, IqTimeout
@@ -33,7 +34,8 @@ from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.plugins import PluginManager, load_plugin from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -146,7 +148,7 @@ class BaseXMPP(XMLStream):
#: A reference to :mod:`sleekxmpp.stanza` to make accessing #: A reference to :mod:`sleekxmpp.stanza` to make accessing
#: stanza classes easier. #: stanza classes easier.
self.stanza = stanza self.stanza = sleekxmpp.stanza
self.register_handler( self.register_handler(
Callback('IM', Callback('IM',
@@ -199,6 +201,7 @@ class BaseXMPP(XMLStream):
# Initialize a few default stanza plugins. # Initialize a few default stanza plugins.
register_stanza_plugin(Iq, Roster) register_stanza_plugin(Iq, Roster)
register_stanza_plugin(Message, Nick) register_stanza_plugin(Message, Nick)
register_stanza_plugin(Message, HTMLIM)
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
"""Save the stream ID once the streams have been established. """Save the stream ID once the streams have been established.

View File

@@ -96,7 +96,6 @@ class ClientXMPP(BaseXMPP):
self.add_event_handler('connected', self._reset_connection_state) self.add_event_handler('connected', self._reset_connection_state)
self.add_event_handler('session_bind', self._handle_session_bind) self.add_event_handler('session_bind', self._handle_session_bind)
self.add_event_handler('roster_update', self._handle_roster)
self.register_stanza(StreamFeatures) self.register_stanza(StreamFeatures)
@@ -107,7 +106,7 @@ class ClientXMPP(BaseXMPP):
self.register_handler( self.register_handler(
Callback('Roster Update', Callback('Roster Update',
StanzaPath('iq@type=set/roster'), StanzaPath('iq@type=set/roster'),
lambda iq: self.event('roster_update', iq))) self._handle_roster))
# Setup default stream features # Setup default stream features
self.register_plugin('feature_starttls') self.register_plugin('feature_starttls')
@@ -115,10 +114,8 @@ class ClientXMPP(BaseXMPP):
self.register_plugin('feature_session') self.register_plugin('feature_session')
self.register_plugin('feature_rosterver') self.register_plugin('feature_rosterver')
self.register_plugin('feature_preapproval') self.register_plugin('feature_preapproval')
self.register_plugin('feature_mechanisms') self.register_plugin('feature_mechanisms',
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
if sasl_mech:
self['feature_mechanisms'].use_mech = sasl_mech
@property @property
def password(self): def password(self):
@@ -136,7 +133,7 @@ class ClientXMPP(BaseXMPP):
be attempted. If that fails, the server user in the JID be attempted. If that fails, the server user in the JID
will be used. will be used.
:param address: A tuple containing the server's host and port. :param address -- A tuple containing the server's host and port.
:param reattempt: If ``True``, repeat attempting to connect if an :param reattempt: If ``True``, repeat attempting to connect if an
error occurs. Defaults to ``True``. error occurs. Defaults to ``True``.
:param use_tls: Indicates if TLS should be used for the :param use_tls: Indicates if TLS should be used for the
@@ -155,6 +152,8 @@ class ClientXMPP(BaseXMPP):
address = (self.boundjid.host, 5222) address = (self.boundjid.host, 5222)
self.dns_service = 'xmpp-client' self.dns_service = 'xmpp-client'
self._expected_server_name = self.boundjid.host
return XMLStream.connect(self, address[0], address[1], return XMLStream.connect(self, address[0], address[1],
use_tls=use_tls, use_ssl=use_ssl, use_tls=use_tls, use_ssl=use_ssl,
reattempt=reattempt) reattempt=reattempt)
@@ -243,22 +242,14 @@ class ClientXMPP(BaseXMPP):
if 'rosterver' in self.features: if 'rosterver' in self.features:
iq['roster']['ver'] = self.client_roster.version iq['roster']['ver'] = self.client_roster.version
if not block and callback is None:
if not block or callback is not None: callback = lambda resp: self._handle_roster(resp)
block = False
if callback is None:
callback = lambda resp: self.event('roster_update', resp)
else:
orig_cb = callback
def wrapped(resp):
self.event('roster_update', resp)
orig_cb(resp)
callback = wrapped
response = iq.send(block, timeout, callback) response = iq.send(block, timeout, callback)
self.event('roster_received', response)
if block: if block:
self.event('roster_update', response) self._handle_roster(response)
return response return response
def _reset_connection_state(self, event=None): def _reset_connection_state(self, event=None):
@@ -309,6 +300,7 @@ class ClientXMPP(BaseXMPP):
roster[jid].save(remove=(item['subscription'] == 'remove')) roster[jid].save(remove=(item['subscription'] == 'remove'))
self.event("roster_update", iq)
if iq['type'] == 'set': if iq['type'] == 'set':
resp = self.Iq(stype='result', resp = self.Iq(stype='result',
sto=iq['from'], sto=iq['from'],

View File

@@ -123,6 +123,12 @@ class ComponentXMPP(BaseXMPP):
""" """
if xml.tag.startswith('{jabber:client}'): if xml.tag.startswith('{jabber:client}'):
xml.tag = xml.tag.replace('jabber:client', self.default_ns) xml.tag = xml.tag.replace('jabber:client', self.default_ns)
# The incoming_filter call is only made on top level stanza
# elements. So we manually continue filtering on sub-elements.
for sub in xml:
self.incoming_filter(sub)
return xml return xml
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
@@ -152,8 +158,8 @@ class ComponentXMPP(BaseXMPP):
""" """
self.session_bind_event.set() self.session_bind_event.set()
self.session_started_event.set() self.session_started_event.set()
self.event('session_bind', self.boundjid, direct=True) self.event("session_bind", self.boundjid, direct=True)
self.event('session_start') self.event("session_start")
def _handle_probe(self, pres): def _handle_probe(self, pres):
self.roster[pres['to']][pres['from']].handle_probe(pres) self.roster[pres['to']][pres['from']].handle_probe(pres)

View File

@@ -42,7 +42,7 @@ class XMPPError(Exception):
Defaults to ``True``. Defaults to ``True``.
""" """
def __init__(self, condition='undefined-condition', text='', def __init__(self, condition='undefined-condition', text=None,
etype='cancel', extension=None, extension_ns=None, etype='cancel', extension=None, extension_ns=None,
extension_args=None, clear=True): extension_args=None, clear=True):
if extension_args is None: if extension_args is None:

View File

@@ -12,7 +12,7 @@ from sleekxmpp.jid import JID
from sleekxmpp.stanza import Iq, StreamFeatures from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.features.feature_bind import stanza from sleekxmpp.features.feature_bind import stanza
from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin from sleekxmpp.plugins import BasePlugin, register_plugin
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -41,12 +41,12 @@ class FeatureBind(BasePlugin):
Arguments: Arguments:
features -- The stream features stanza. features -- The stream features stanza.
""" """
log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource) log.debug("Requesting resource: %s", self.xmpp.boundjid.resource)
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'set' iq['type'] = 'set'
iq.enable('bind') iq.enable('bind')
if self.xmpp.requested_jid.resource: if self.xmpp.boundjid.resource:
iq['bind']['resource'] = self.xmpp.requested_jid.resource iq['bind']['resource'] = self.xmpp.boundjid.resource
response = iq.send(now=True) response = iq.send(now=True)
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
@@ -56,10 +56,10 @@ class FeatureBind(BasePlugin):
self.xmpp.features.add('bind') self.xmpp.features.add('bind')
log.info("JID set to: %s", self.xmpp.boundjid.full) log.info("Node set to: %s", self.xmpp.boundjid.full)
if 'session' not in features['features']: if 'session' not in features['features']:
log.debug("Established Session") log.debug("Established Session")
self.xmpp.sessionstarted = True self.xmpp.sessionstarted = True
self.xmpp.session_started_event.set() self.xmpp.session_started_event.set()
self.xmpp.event('session_start') self.xmpp.event("session_start")

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import sys
import ssl import ssl
import logging import logging
@@ -43,16 +44,15 @@ class FeatureMechanisms(BasePlugin):
} }
def plugin_init(self): def plugin_init(self):
if not self.use_mech and not self.xmpp.requested_jid.user:
self.use_mech = 'ANONYMOUS'
if self.sasl_callback is None: if self.sasl_callback is None:
self.sasl_callback = self._default_credentials self.sasl_callback = self._default_credentials
if self.security_callback is None: if self.security_callback is None:
self.security_callback = self._default_security self.security_callback = self._default_security
creds = self.sasl_callback(set(['username']), set())
if not self.use_mech and not creds['username']:
self.use_mech = 'ANONYMOUS'
self.mech = None self.mech = None
self.mech_list = set() self.mech_list = set()
self.attempted_mechs = set() self.attempted_mechs = set()
@@ -92,26 +92,27 @@ class FeatureMechanisms(BasePlugin):
values = required_values.union(optional_values) values = required_values.union(optional_values)
for value in values: for value in values:
if value == 'username': if value == 'username':
result[value] = creds.get('username', self.xmpp.requested_jid.user) result[value] = self.xmpp.requested_jid.user
elif value == 'password':
result[value] = creds['password']
elif value == 'authzid':
result[value] = creds.get('authzid', '')
elif value == 'email': elif value == 'email':
jid = self.xmpp.requested_jid.bare jid = self.xmpp.requested_jid.bare
result[value] = creds.get('email', jid) result[value] = creds.get('email', jid)
elif value == 'channel_binding': elif value == 'channel_binding':
if hasattr(self.xmpp.socket, 'get_channel_binding'): if sys.version_info >= (3, 3):
result[value] = self.xmpp.socket.get_channel_binding() result[value] = self.xmpp.socket.get_channel_binding()
else: else:
log.debug("Channel binding not supported.")
log.debug("Use Python 3.3+ for channel binding and " + \
"SCRAM-SHA-1-PLUS support")
result[value] = None result[value] = None
elif value == 'host': elif value == 'host':
result[value] = creds.get('host', self.xmpp.requested_jid.domain) result[value] = self.xmpp.requested_jid.domain
elif value == 'realm': elif value == 'realm':
result[value] = creds.get('realm', self.xmpp.requested_jid.domain) result[value] = self.xmpp.requested_jid.domain
elif value == 'service-name': elif value == 'service-name':
result[value] = creds.get('service-name', self.xmpp._service_name) result[value] = self.xmpp._service_name
elif value == 'service': elif value == 'service':
result[value] = creds.get('service', 'xmpp') result[value] = 'xmpp'
elif value in creds: elif value in creds:
result[value] = creds[value] result[value] = creds[value]
return result return result
@@ -173,12 +174,8 @@ class FeatureMechanisms(BasePlugin):
except sasl.SASLNoAppropriateMechanism: except sasl.SASLNoAppropriateMechanism:
log.error("No appropriate login method.") log.error("No appropriate login method.")
self.xmpp.event("no_auth", direct=True) self.xmpp.event("no_auth", direct=True)
self.xmpp.event("failed_auth", direct=True)
self.attempted_mechs = set() self.attempted_mechs = set()
return self.xmpp.disconnect() return self.xmpp.disconnect()
except StringPrepError:
log.exception("A credential value did not pass SASLprep.")
self.xmpp.disconnect()
resp = stanza.Auth(self.xmpp) resp = stanza.Auth(self.xmpp)
resp['mechanism'] = self.mech.name resp['mechanism'] = self.mech.name
@@ -195,6 +192,9 @@ class FeatureMechanisms(BasePlugin):
"A security breach is possible.") "A security breach is possible.")
self.attempted_mechs.add(self.mech.name) self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect() self.xmpp.disconnect()
except StringPrepError:
log.exception("A credential value did not pass SASLprep.")
self.xmpp.disconnect()
else: else:
resp.send(now=True) resp.send(now=True)
@@ -215,8 +215,6 @@ class FeatureMechanisms(BasePlugin):
self.attempted_mechs.add(self.mech.name) self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect() self.xmpp.disconnect()
else: else:
if resp.get_value() == '':
resp.del_value()
resp.send(now=True) resp.send(now=True)
def _handle_success(self, stanza): def _handle_success(self, stanza):

View File

@@ -40,7 +40,7 @@ class Auth(StanzaBase):
if not self['mechanism'] in self.plain_mechs: if not self['mechanism'] in self.plain_mechs:
if values: if values:
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
elif values == b'': else:
self.xml.text = '=' self.xml.text = '='
else: else:
self.xml.text = bytes(values).decode('utf-8') self.xml.text = bytes(values).decode('utf-8')

View File

@@ -8,7 +8,7 @@
import logging import logging
from sleekxmpp.stanza import StreamFeatures from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.features.feature_preapproval import stanza from sleekxmpp.features.feature_preapproval import stanza
from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin from sleekxmpp.plugins.base import BasePlugin

View File

@@ -8,7 +8,7 @@
import logging import logging
from sleekxmpp.stanza import StreamFeatures from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.features.feature_rosterver import stanza from sleekxmpp.features.feature_rosterver import stanza
from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin from sleekxmpp.plugins.base import BasePlugin

View File

@@ -51,4 +51,4 @@ class FeatureSession(BasePlugin):
log.debug("Established Session") log.debug("Established Session")
self.xmpp.sessionstarted = True self.xmpp.sessionstarted = True
self.xmpp.session_started_event.set() self.xmpp.session_started_event.set()
self.xmpp.event('session_start') self.xmpp.event("session_start")

View File

@@ -19,8 +19,6 @@ import stringprep
import threading import threading
import encodings.idna import encodings.idna
from copy import deepcopy
from sleekxmpp.util import stringprep_profiles from sleekxmpp.util import stringprep_profiles
from sleekxmpp.thirdparty import OrderedDict from sleekxmpp.thirdparty import OrderedDict
@@ -77,7 +75,7 @@ def _cache(key, parts, locked):
with JID_CACHE_LOCK: with JID_CACHE_LOCK:
while len(JID_CACHE) > JID_CACHE_MAX_SIZE: while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
found = None found = None
for key, item in JID_CACHE.items(): for key, item in JID_CACHE.iteritems():
if not item[1]: # if not locked if not item[1]: # if not locked
found = key found = key
break break
@@ -204,7 +202,7 @@ def _validate_domain(domain):
socket.inet_pton(socket.AF_INET6, domain.strip('[]')) socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
domain = '[%s]' % domain.strip('[]') domain = '[%s]' % domain.strip('[]')
ip_addr = True ip_addr = True
except (socket.error, ValueError): except socket.error:
pass pass
if not ip_addr: if not ip_addr:
@@ -230,7 +228,7 @@ def _validate_domain(domain):
for char in label: for char in label:
if char in ILLEGAL_CHARS: if char in ILLEGAL_CHARS:
raise InvalidJID('Domain contains illegal characters') raise InvalidJID('Domain contains illegar characters')
if '-' in (label[0], label[-1]): if '-' in (label[0], label[-1]):
raise InvalidJID('Domain started or ended with -') raise InvalidJID('Domain started or ended with -')
@@ -508,100 +506,50 @@ class JID(object):
""" """
self._jid = JID(data)._jid self._jid = JID(data)._jid
@property # pylint: disable=R0911
def resource(self): def __getattr__(self, name):
return self._jid[2] or '' """Retrieve the given JID component.
@property :param name: one of: user, server, domain, resource,
def user(self): full, or bare.
return self._jid[0] or '' """
if name == 'resource':
return self._jid[2] or ''
elif name in ('user', 'username', 'local', 'node'):
return self._jid[0] or ''
elif name in ('server', 'domain', 'host'):
return self._jid[1] or ''
elif name in ('full', 'jid'):
return _format_jid(*self._jid)
elif name == 'bare':
return _format_jid(self._jid[0], self._jid[1])
elif name == '_jid':
return getattr(super(JID, self), '_jid')
else:
return None
@property # pylint: disable=W0212
def local(self): def __setattr__(self, name, value):
return self._jid[0] or '' """Update the given JID component.
@property
def node(self):
return self._jid[0] or ''
@property
def username(self):
return self._jid[0] or ''
@property
def bare(self):
return _format_jid(self._jid[0], self._jid[1])
@property
def server(self):
return self._jid[1] or ''
@property
def domain(self):
return self._jid[1] or ''
@property
def host(self):
return self._jid[1] or ''
@property
def full(self):
return _format_jid(*self._jid)
@property
def jid(self):
return _format_jid(*self._jid)
@property
def bare(self):
return _format_jid(self._jid[0], self._jid[1])
@resource.setter
def resource(self, value):
self._jid = JID(self, resource=value)._jid
@user.setter
def user(self, value):
self._jid = JID(self, local=value)._jid
@username.setter
def username(self, value):
self._jid = JID(self, local=value)._jid
@local.setter
def local(self, value):
self._jid = JID(self, local=value)._jid
@node.setter
def node(self, value):
self._jid = JID(self, local=value)._jid
@server.setter
def server(self, value):
self._jid = JID(self, domain=value)._jid
@domain.setter
def domain(self, value):
self._jid = JID(self, domain=value)._jid
@host.setter
def host(self, value):
self._jid = JID(self, domain=value)._jid
@full.setter
def full(self, value):
self._jid = JID(value)._jid
@jid.setter
def jid(self, value):
self._jid = JID(value)._jid
@bare.setter
def bare(self, value):
parsed = JID(value)._jid
self._jid = (parsed[0], parsed[1], self._jid[2])
:param name: one of: ``user``, ``username``, ``local``,
``node``, ``server``, ``domain``, ``host``,
``resource``, ``full``, ``jid``, or ``bare``.
:param value: The new string value of the JID component.
"""
if name == '_jid':
super(JID, self).__setattr__('_jid', value)
elif name == 'resource':
self._jid = JID(self, resource=value)._jid
elif name in ('user', 'username', 'local', 'node'):
self._jid = JID(self, local=value)._jid
elif name in ('server', 'domain', 'host'):
self._jid = JID(self, domain=value)._jid
elif name in ('full', 'jid'):
self._jid = JID(value)._jid
elif name == 'bare':
parsed = JID(value)._jid
self._jid = (parsed[0], parsed[1], self._jid[2])
def __str__(self): def __str__(self):
"""Use the full JID as the string value.""" """Use the full JID as the string value."""
@@ -632,7 +580,3 @@ class JID(object):
def __copy__(self): def __copy__(self):
"""Generate a duplicate JID.""" """Generate a duplicate JID."""
return JID(self) return JID(self)
def __deepcopy__(self, memo):
"""Generate a duplicate JID."""
return JID(deepcopy(str(self), memo))

View File

@@ -11,30 +11,28 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin
__all__ = [ __all__ = [
# Non-standard
'gmail_notify', # Gmail searching and notifications
# XEPS # XEPS
'xep_0004', # Data Forms 'xep_0004', # Data Forms
'xep_0009', # Jabber-RPC 'xep_0009', # Jabber-RPC
'xep_0012', # Last Activity 'xep_0012', # Last Activity
'xep_0013', # Flexible Offline Message Retrieval 'xep_0013', # Flexible Offline Message Retrieval
'xep_0016', # Privacy Lists 'xep_0016', # Privacy Lists
'xep_0020', # Feature Negotiation
'xep_0027', # Current Jabber OpenPGP Usage 'xep_0027', # Current Jabber OpenPGP Usage
'xep_0030', # Service Discovery 'xep_0030', # Service Discovery
'xep_0033', # Extended Stanza Addresses 'xep_0033', # Extended Stanza Addresses
'xep_0045', # Multi-User Chat (Client) 'xep_0045', # Multi-User Chat (Client)
'xep_0047', # In-Band Bytestreams 'xep_0047', # In-Band Bytestreams
'xep_0048', # Bookmarks
'xep_0049', # Private XML Storage 'xep_0049', # Private XML Storage
'xep_0050', # Ad-hoc Commands 'xep_0050', # Ad-hoc Commands
'xep_0054', # vcard-temp 'xep_0054', # vcard-temp
'xep_0059', # Result Set Management 'xep_0059', # Result Set Management
'xep_0060', # Pubsub (Client) 'xep_0060', # Pubsub (Client)
'xep_0065', # SOCKS5 Bytestreams
'xep_0066', # Out of Band Data 'xep_0066', # Out of Band Data
'xep_0071', # XHTML-IM
'xep_0077', # In-Band Registration 'xep_0077', # In-Band Registration
# 'xep_0078', # Non-SASL auth. Don't automatically load # 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0079', # Advanced Message Processing
'xep_0080', # User Location 'xep_0080', # User Location
'xep_0082', # XMPP Date and Time Profiles 'xep_0082', # XMPP Date and Time Profiles
'xep_0084', # User Avatar 'xep_0084', # User Avatar
@@ -50,14 +48,12 @@ __all__ = [
'xep_0128', # Extended Service Discovery 'xep_0128', # Extended Service Discovery
'xep_0131', # Standard Headers and Internet Metadata 'xep_0131', # Standard Headers and Internet Metadata
'xep_0133', # Service Administration 'xep_0133', # Service Administration
'xep_0152', # Reachability Addresses
'xep_0153', # vCard-Based Avatars 'xep_0153', # vCard-Based Avatars
'xep_0163', # Personal Eventing Protocol 'xep_0163', # Personal Eventing Protocol
'xep_0172', # User Nickname 'xep_0172', # User Nickname
'xep_0184', # Message Receipts 'xep_0184', # Message Receipts
'xep_0186', # Invisible Command 'xep_0186', # Invisible Command
'xep_0191', # Blocking Command 'xep_0191', # Blocking Command
'xep_0196', # User Gaming
'xep_0198', # Stream Management 'xep_0198', # Stream Management
'xep_0199', # Ping 'xep_0199', # Ping
'xep_0202', # Entity Time 'xep_0202', # Entity Time
@@ -80,7 +76,4 @@ __all__ = [
'xep_0302', # XMPP Compliance Suites 2012 'xep_0302', # XMPP Compliance Suites 2012
'xep_0308', # Last Message Correction 'xep_0308', # Last Message Correction
'xep_0313', # Message Archive Management 'xep_0313', # Message Archive Management
'xep_0319', # Last User Interaction in Presence
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
] ]

View File

@@ -1,47 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin, BasePlugin
from sleekxmpp.plugins.google.gmail import Gmail
from sleekxmpp.plugins.google.auth import GoogleAuth
from sleekxmpp.plugins.google.settings import GoogleSettings
from sleekxmpp.plugins.google.nosave import GoogleNoSave
class Google(BasePlugin):
"""
Google: Custom GTalk Features
Also see: <https://developers.google.com/talk/jep_extensions/extensions>
"""
name = 'google'
description = 'Google: Custom GTalk Features'
dependencies = set([
'gmail',
'google_settings',
'google_nosave',
'google_auth'
])
def __getitem__(self, attr):
if attr in ('settings', 'nosave', 'auth'):
return self.xmpp['google_%s' % attr]
elif attr == 'gmail':
return self.xmpp['gmail']
else:
raise KeyError(attr)
register_plugin(Gmail)
register_plugin(GoogleAuth)
register_plugin(GoogleSettings)
register_plugin(GoogleNoSave)
register_plugin(Google)

View File

@@ -1,10 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.auth import stanza
from sleekxmpp.plugins.google.auth.auth import GoogleAuth

View File

@@ -1,52 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.auth import stanza
log = logging.getLogger(__name__)
class GoogleAuth(BasePlugin):
"""
Google: Auth Extensions (JID Domain Discovery, OAuth2)
Also see:
<https://developers.google.com/talk/jep_extensions/jid_domain_change>
<https://developers.google.com/talk/jep_extensions/oauth>
"""
name = 'google_auth'
description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
dependencies = set(['feature_mechanisms'])
stanza = stanza
def plugin_init(self):
self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
stanza.GoogleAuth)
self.xmpp.add_filter('out', self._auth)
def plugin_end(self):
self.xmpp.del_filter('out', self._auth)
def _auth(self, stanza):
if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
stanza.stream = self.xmpp
stanza['google']['client_uses_full_bind_result'] = True
if stanza['mechanism'] == 'X-OAUTH2':
stanza['google']['service'] = 'oauth2'
print(stanza)
return stanza

View File

@@ -1,49 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase, ET
class GoogleAuth(ElementBase):
name = 'auth'
namespace = 'http://www.google.com/talk/protocol/auth'
plugin_attrib = 'google'
interfaces = set(['client_uses_full_bind_result', 'service'])
discovery_attr= '{%s}client-uses-full-bind-result' % namespace
service_attr= '{%s}service' % namespace
def setup(self, xml):
"""Don't create XML for the plugin."""
self.xml = ET.Element('')
print('setting up google extension')
def get_client_uses_full_bind_result(self):
return self.parent()._get_attr(self.disovery_attr) == 'true'
def set_client_uses_full_bind_result(self, value):
print('>>>', value)
if value in (True, 'true'):
self.parent()._set_attr(self.discovery_attr, 'true')
else:
self.parent()._del_attr(self.discovery_attr)
def del_client_uses_full_bind_result(self):
self.parent()._del_attr(self.discovery_attr)
def get_service(self):
return self.parent()._get_attr(self.service_attr, '')
def set_service(self, value):
if value:
self.parent()._set_attr(self.service_attr, value)
else:
self.parent()._del_attr(self.service_attr)
def del_service(self):
self.parent()._del_attr(self.service_attr)

View File

@@ -1,10 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.gmail import stanza
from sleekxmpp.plugins.google.gmail.notifications import Gmail

View File

@@ -1,96 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.gmail import stanza
log = logging.getLogger(__name__)
class Gmail(BasePlugin):
"""
Google: Gmail Notifications
Also see <https://developers.google.com/talk/jep_extensions/gmail>.
"""
name = 'gmail'
description = 'Google: Gmail Notifications'
dependencies = set()
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, stanza.GmailQuery)
register_stanza_plugin(Iq, stanza.MailBox)
register_stanza_plugin(Iq, stanza.NewMail)
self.xmpp.register_handler(
Callback('Gmail New Mail',
MatchXPath('{%s}iq/{%s}%s' % (
self.xmpp.default_ns,
stanza.NewMail.namespace,
stanza.NewMail.name)),
self._handle_new_mail))
self._last_result_time = None
self._last_result_tid = None
def plugin_end(self):
self.xmpp.remove_handler('Gmail New Mail')
def _handle_new_mail(self, iq):
log.info('Gmail: New email!')
iq.reply().send()
self.xmpp.event('gmail_notification')
def check(self, block=True, timeout=None, callback=None):
last_time = self._last_result_time
last_tid = self._last_result_tid
if not block:
callback = lambda iq: self._update_last_results(iq, callback)
resp = self.search(newer_time=last_time,
newer_tid=last_tid,
block=block,
timeout=timeout,
callback=callback)
if block:
self._update_last_results(resp)
return resp
def _update_last_results(self, iq, callback=None):
self._last_result_time = data['gmail_messages']['result_time']
threads = data['gmail_messages']['threads']
if threads:
self._last_result_tid = threads[0]['tid']
if callback:
callback(iq)
def search(self, query=None, newer_time=None, newer_tid=None, block=True,
timeout=None, callback=None):
if not query:
log.info('Gmail: Checking for new email')
else:
log.info('Gmail: Searching for emails matching: "%s"', query)
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = self.xmpp.boundjid.bare
iq['gmail']['search'] = query
iq['gmail']['newer_than_time'] = newer_time
iq['gmail']['newer_than_tid'] = newer_tid
return iq.send(block=block, timeout=timeout, callback=callback)

View File

@@ -1,101 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
plugin_attrib = 'gmail'
interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
def get_search(self):
return self._get_attr('q', '')
def set_search(self, search):
self._set_attr('q', search)
def del_search(self):
self._del_attr('q')
def get_newer_than_time(self):
return self._get_attr('newer-than-time', '')
def set_newer_than_time(self, value):
self._set_attr('newer-than-time', value)
def del_newer_than_time(self):
self._del_attr('newer-than-time')
def get_newer_than_tid(self):
return self._get_attr('newer-than-tid', '')
def set_newer_than_tid(self, value):
self._set_attr('newer-than-tid', value)
def del_newer_than_tid(self):
self._del_attr('newer-than-tid')
class MailBox(ElementBase):
namespace = 'google:mail:notify'
name = 'mailbox'
plugin_attrib = 'gmail_messages'
interfaces = set(['result_time', 'url', 'matched', 'estimate'])
def get_matched(self):
return self._get_attr('total-matched', '')
def get_estimate(self):
return self._get_attr('total-estimate', '') == '1'
def get_result_time(self):
return self._get_attr('result-time', '')
class MailThread(ElementBase):
namespace = 'google:mail:notify'
name = 'mail-thread-info'
plugin_attrib = 'thread'
plugin_multi_attrib = 'threads'
interfaces = set(['tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'])
sub_interfaces = set(['labels', 'subject', 'snippet'])
def get_senders(self):
result = []
senders = self.xml.findall('{%s}senders/{%s}sender' % (
self.namespace, self.namespace))
for sender in senders:
result.append(MailSender(xml=sender))
return result
class MailSender(ElementBase):
namespace = 'google:mail:notify'
name = 'sender'
plugin_attrib = name
interfaces = set(['address', 'name', 'originator', 'unread'])
def get_originator(self):
return self.xml.attrib.get('originator', '0') == '1'
def get_unread(self):
return self.xml.attrib.get('unread', '0') == '1'
class NewMail(ElementBase):
namespace = 'google:mail:notify'
name = 'new-mail'
plugin_attrib = 'gmail_notification'
register_stanza_plugin(MailBox, MailThread, iterable=True)

View File

@@ -1,10 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.nosave import stanza
from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave

View File

@@ -1,83 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq, Message
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.nosave import stanza
log = logging.getLogger(__name__)
class GoogleNoSave(BasePlugin):
"""
Google: Off the Record Chats
NOTE: This is NOT an encryption method.
Also see <https://developers.google.com/talk/jep_extensions/otr>.
"""
name = 'google_nosave'
description = 'Google: Off the Record Chats'
dependencies = set(['google_settings'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.NoSave)
register_stanza_plugin(Iq, stanza.NoSaveQuery)
self.xmpp.register_handler(
Callback('Google Nosave',
StanzaPath('iq@type=set/google_nosave'),
self._handle_nosave_change))
def plugin_end(self):
self.xmpp.remove_handler('Google Nosave')
def enable(self, jid=None, block=True, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': False},
block=block, timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = True
return iq.send(block=block, timeout=timeout, callback=callback)
def disable(self, jid=None, block=True, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': True},
block=block, timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = False
return iq.send(block=block, timeout=timeout, callback=callback)
def get(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_nosave')
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_nosave_change(self, iq):
reply = self.xmpp.Iq()
reply['type'] = 'result'
reply['id'] = iq['id']
reply['to'] = iq['from']
reply.send()
self.xmpp.event('google_nosave_change', iq)

View File

@@ -1,59 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.jid import JID
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class NoSave(ElementBase):
name = 'x'
namespace = 'google:nosave'
plugin_attrib = 'google_nosave'
interfaces = set(['value'])
def get_value(self):
return self._get_attr('value', '') == 'enabled'
def set_value(self, value):
self._set_attr('value', 'enabled' if value else 'disabled')
class NoSaveQuery(ElementBase):
name = 'query'
namespace = 'google:nosave'
plugin_attrib = 'google_nosave'
interfaces = set()
class Item(ElementBase):
name = 'item'
namespace = 'google:nosave'
plugin_attrib = 'item'
plugin_multi_attrib = 'items'
interfaces = set(['jid', 'source', 'value'])
def get_value(self):
return self._get_attr('value', '') == 'enabled'
def set_value(self, value):
self._set_attr('value', 'enabled' if value else 'disabled')
def get_jid(self):
return JID(self._get_attr('jid', ''))
def set_jid(self, value):
self._set_attr('jid', str(value))
def get_source(self):
return JID(self._get_attr('source', ''))
def set_source(self):
self._set_attr('source', str(value))
register_stanza_plugin(NoSaveQuery, Item)

View File

@@ -1,10 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.settings import stanza
from sleekxmpp.plugins.google.settings.settings import GoogleSettings

View File

@@ -1,65 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.settings import stanza
class GoogleSettings(BasePlugin):
"""
Google: Gmail Notifications
Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
"""
name = 'google_settings'
description = 'Google: User Settings'
dependencies = set()
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, stanza.UserSettings)
self.xmpp.register_handler(
Callback('Google Settings',
StanzaPath('iq@type=set/google_settings'),
self._handle_settings_change))
def plugin_end(self):
self.xmpp.remove_handler('Google Settings')
def get(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_settings')
return iq.send(block=block, timeout=timeout, callback=callback)
def update(self, settings, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('google_settings')
for setting, value in settings.items():
iq['google_settings'][setting] = value
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_settings_change(self, iq):
reply = self.xmpp.Iq()
reply['type'] = 'result'
reply['id'] = iq['id']
reply['to'] = iq['from']
reply.send()
self.xmpp.event('google_settings_change', iq)

View File

@@ -1,110 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class UserSettings(ElementBase):
name = 'usersetting'
namespace = 'google:setting'
plugin_attrib = 'google_settings'
interfaces = set(['auto_accept_suggestions',
'mail_notifications',
'archiving_enabled',
'gmail',
'email_verified',
'domain_privacy_notice',
'display_name'])
def _get_setting(self, setting):
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
if xml is not None:
return xml.attrib.get('value', '') == 'true'
return False
def _set_setting(self, setting, value):
self._del_setting(setting)
if value in (True, False):
xml = ET.Element('{%s}%s' % (self.namespace, setting))
xml.attrib['value'] = 'true' if value else 'false'
self.xml.append(xml)
def _del_setting(self, setting):
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
if xml is not None:
self.xml.remove(xml)
def get_display_name(self):
xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
if xml is not None:
return xml.attrib.get('value', '')
return ''
def set_display_name(self, value):
self._del_setting(setting)
if value:
xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
xml.attrib['value'] = value
self.xml.append(xml)
def del_display_name(self):
self._del_setting('displayname')
def get_auto_accept_suggestions(self):
return self._get_setting('autoacceptsuggestions')
def get_mail_notifications(self):
return self._get_setting('mailnotifications')
def get_archiving_enabled(self):
return self._get_setting('archivingenabled')
def get_gmail(self):
return self._get_setting('gmail')
def get_email_verified(self):
return self._get_setting('emailverified')
def get_domain_privacy_notice(self):
return self._get_setting('domainprivacynotice')
def set_auto_accept_suggestions(self, value):
self._set_setting('autoacceptsuggestions', value)
def set_mail_notifications(self, value):
self._set_setting('mailnotifications', value)
def set_archiving_enabled(self, value):
self._set_setting('archivingenabled', value)
def set_gmail(self, value):
self._set_setting('gmail', value)
def set_email_verified(self, value):
self._set_setting('emailverified', value)
def set_domain_privacy_notice(self, value):
self._set_setting('domainprivacynotice', value)
def del_auto_accept_suggestions(self):
self._del_setting('autoacceptsuggestions')
def del_mail_notifications(self):
self._del_setting('mailnotifications')
def del_archiving_enabled(self):
self._del_setting('archivingenabled')
def del_gmail(self):
self._del_setting('gmail')
def del_email_verified(self):
self._del_setting('emailverified')
def del_domain_privacy_notice(self):
self._del_setting('domainprivacynotice')

49
sleekxmpp/plugins/jobs.py Normal file
View File

@@ -0,0 +1,49 @@
from . import base
import logging
from xml.etree import cElementTree as ET
log = logging.getLogger(__name__)
class jobs(base.base_plugin):
def plugin_init(self):
self.xep = 'pubsubjob'
self.description = "Job distribution over Pubsub"
def post_init(self):
pass
#TODO add event
def createJobNode(self, host, jid, node, config=None):
pass
def createJob(self, host, node, jobid=None, payload=None):
return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),))
def claimJob(self, host, node, jobid, ifrom=None):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
def unclaimJob(self, host, node, jobid):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
def finishJob(self, host, node, jobid, payload=None):
finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished')
if payload is not None:
finished.append(payload)
return self._setState(host, node, jobid, finished)
def _setState(self, host, node, jobid, state, ifrom=None):
iq = self.xmpp.Iq()
iq['to'] = host
if ifrom: iq['from'] = ifrom
iq['type'] = 'set'
iq['psstate']['node'] = node
iq['psstate']['item'] = jobid
iq['psstate']['payload'] = state
result = iq.send()
if result is None or type(result) == bool or result['type'] != 'result':
log.error("Unable to change %s:%s to %s", node, jobid, state)
return False
return True

View File

@@ -0,0 +1,421 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
import logging
#TODO support item groups and results
log = logging.getLogger(__name__)
class old_0004(base.base_plugin):
def plugin_init(self):
self.xep = '0004'
self.description = '*Deprecated Data Forms'
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form')
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
log.warning("This implementation of XEP-0004 is deprecated.")
def handler_message_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("message_form", object)
def handler_presence_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("presence_form", object)
def handle_form(self, xml):
xmlform = xml.find('{jabber:x:data}x')
object = self.buildForm(xmlform)
self.xmpp.event("message_xform", object)
return object
def buildForm(self, xml):
form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
def makeForm(self, ftype='form', title='', instructions=''):
return Form(self.xmpp, ftype, title, instructions)
class FieldContainer(object):
def __init__(self, stanza = 'form'):
self.fields = []
self.field = {}
self.stanza = stanza
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
self.field[var] = FormField(var, ftype, label, desc, required, value)
self.fields.append(self.field[var])
return self.field[var]
def buildField(self, xml):
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
self.fields.append(self.field[xml.get('var', '__unnamed__')])
self.field[xml.get('var', '__unnamed__')].buildField(xml)
def buildContainer(self, xml):
self.stanza = xml.tag
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
def getXML(self, ftype):
container = ET.Element(self.stanza)
for field in self.fields:
container.append(field.getXML(ftype))
return container
class Form(FieldContainer):
types = ('form', 'submit', 'cancel', 'result')
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
if not ftype in self.types:
raise ValueError("Invalid Form Type")
FieldContainer.__init__(self)
self.xmpp = xmpp
self.type = ftype
self.title = title
self.instructions = instructions
self.reported = []
self.items = []
def merge(self, form2):
form1 = Form(ftype=self.type)
form1.fromXML(self.getXML(self.type))
for field in form2.fields:
if not field.var in form1.field:
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
else:
form1.field[field.var].value = field.value
for option, label in field.options:
if (option, label) not in form1.field[field.var].options:
form1.fields[field.var].addOption(option, label)
return form1
def copy(self):
newform = Form(ftype=self.type)
newform.fromXML(self.getXML(self.type))
return newform
def update(self, form):
values = form.getValues()
for var in values:
if var in self.fields:
self.fields[var].setValue(self.fields[var])
def getValues(self):
result = {}
for field in self.fields:
value = field.value
if len(value) == 1:
value = value[0]
result[field.var] = value
return result
def setValues(self, values={}):
for field in values:
if field in self.field:
if isinstance(values[field], list) or isinstance(values[field], tuple):
for value in values[field]:
self.field[field].setValue(value)
else:
self.field[field].setValue(values[field])
def fromXML(self, xml):
self.buildForm(xml)
def addItem(self):
newitem = FieldContainer('item')
self.items.append(newitem)
return newitem
def buildItem(self, xml):
newitem = self.addItem()
newitem.buildContainer(xml)
def addReported(self):
reported = FieldContainer('reported')
self.reported.append(reported)
return reported
def buildReported(self, xml):
reported = self.addReported()
reported.buildContainer(xml)
def setTitle(self, title):
self.title = title
def setInstructions(self, instructions):
self.instructions = instructions
def setType(self, ftype):
self.type = ftype
def getXMLMessage(self, to):
msg = self.xmpp.makeMessage(to)
msg.append(self.getXML())
return msg
def buildForm(self, xml):
self.type = xml.get('type', 'form')
if xml.find('{jabber:x:data}title') is not None:
self.setTitle(xml.find('{jabber:x:data}title').text)
if xml.find('{jabber:x:data}instructions') is not None:
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
for reported in xml.findall('{jabber:x:data}reported'):
self.buildReported(reported)
for item in xml.findall('{jabber:x:data}item'):
self.buildItem(item)
#def getXML(self, tostring = False):
def getXML(self, ftype=None):
if ftype:
self.type = ftype
form = ET.Element('{jabber:x:data}x')
form.attrib['type'] = self.type
if self.title and self.type in ('form', 'result'):
title = ET.Element('{jabber:x:data}title')
title.text = self.title
form.append(title)
if self.instructions and self.type == 'form':
instructions = ET.Element('{jabber:x:data}instructions')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXML(self.type))
for reported in self.reported:
form.append(reported.getXML('{jabber:x:data}reported'))
for item in self.items:
form.append(item.getXML(self.type))
#if tostring:
# form = self.xmpp.tostring(form)
return form
def getXHTML(self):
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
if self.title:
title = ET.Element('h2')
title.text = self.title
form.append(title)
if self.instructions:
instructions = ET.Element('p')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXHTML())
for field in self.reported:
form.append(field.getXHTML())
for field in self.items:
form.append(field.getXHTML())
return form
def makeSubmit(self):
self.setType('submit')
class FormField(object):
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
lbtypes = ('fixed', 'text-multi')
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
if not ftype in self.types:
raise ValueError("Invalid Field Type")
self.type = ftype
self.var = var
self.label = label
self.desc = desc
self.options = []
self.required = False
self.value = []
if self.type in self.listtypes:
self.islist = True
else:
self.islist = False
if self.type in self.lbtypes:
self.islinebreak = True
else:
self.islinebreak = False
if value:
self.setValue(value)
def addOption(self, value, label):
if self.islist:
self.options.append((value, label))
else:
raise ValueError("Cannot add options to non-list type field.")
def setTrue(self):
if self.type == 'boolean':
self.value = [True]
def setFalse(self):
if self.type == 'boolean':
self.value = [False]
def require(self):
self.required = True
def setDescription(self, desc):
self.desc = desc
def setValue(self, value):
if self.type == 'boolean':
if value in ('1', 1, True, 'true', 'True', 'yes'):
value = True
else:
value = False
if self.islinebreak and value is not None:
self.value += value.split('\n')
else:
if len(self.value) and (not self.islist or self.type == 'list-single'):
self.value = [value]
else:
self.value.append(value)
def delValue(self, value):
if type(self.value) == type([]):
try:
idx = self.value.index(value)
if idx != -1:
self.value.pop(idx)
except ValueError:
pass
else:
self.value = ''
def setAnswer(self, value):
self.setValue(value)
def buildField(self, xml):
self.type = xml.get('type', 'text-single')
self.label = xml.get('label', '')
for option in xml.findall('{jabber:x:data}option'):
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
for value in xml.findall('{jabber:x:data}value'):
self.setValue(value.text)
if xml.find('{jabber:x:data}required') is not None:
self.require()
if xml.find('{jabber:x:data}desc') is not None:
self.setDescription(xml.find('{jabber:x:data}desc').text)
def getXML(self, ftype):
field = ET.Element('{jabber:x:data}field')
if ftype != 'result':
field.attrib['type'] = self.type
if self.type != 'fixed':
if self.var:
field.attrib['var'] = self.var
if self.label:
field.attrib['label'] = self.label
if ftype == 'form':
for option in self.options:
optionxml = ET.Element('{jabber:x:data}option')
optionxml.attrib['label'] = option[1]
optionval = ET.Element('{jabber:x:data}value')
optionval.text = option[0]
optionxml.append(optionval)
field.append(optionxml)
if self.required:
required = ET.Element('{jabber:x:data}required')
field.append(required)
if self.desc:
desc = ET.Element('{jabber:x:data}desc')
desc.text = self.desc
field.append(desc)
for value in self.value:
valuexml = ET.Element('{jabber:x:data}value')
if value is True or value is False:
if value:
valuexml.text = '1'
else:
valuexml.text = '0'
else:
valuexml.text = value
field.append(valuexml)
return field
def getXHTML(self):
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
if self.label:
label = ET.Element('p')
label.text = "%s: " % self.label
else:
label = ET.Element('p')
label.text = "%s: " % self.var
field.append(label)
if self.type == 'boolean':
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
if len(self.value) and self.value[0] in (True, 'true', '1'):
formf.attrib['checked'] = 'checked'
elif self.type == 'fixed':
formf = ET.Element('p')
try:
formf.text = ', '.join(self.value)
except:
pass
field.append(formf)
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type == 'hidden':
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type in ('jid-multi', 'list-multi'):
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
optf.text = option[1]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(option)
elif self.type in ('jid-single', 'text-single'):
formf = ET.Element('input', {'type': 'text', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
elif self.type == 'list-single':
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0]})
optf.text = option[1]
if not optf.text:
optf.text = option[0]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(optf)
elif self.type == 'text-multi':
formf = ET.Element('textarea', {'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
if not formf.text:
formf.text = ' '
elif self.type == 'text-private':
formf = ET.Element('input', {'type': 'password', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
label.append(formf)
return field

View File

@@ -0,0 +1,277 @@
"""
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, name='Jabber RPC Call')
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
self._callResult, name='Jabber RPC Result')
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
self._callError, name='Jabber RPC Error')
self.entries = {}
self.activeCalls = []
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
self.xmpp.plugin['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

View File

@@ -0,0 +1,133 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import time
class old_0050(base.base_plugin):
"""
XEP-0050 Ad-Hoc Commands
"""
def plugin_init(self):
self.xep = '0050'
self.description = 'Ad-Hoc Commands'
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
self.commands = {}
self.sessions = {}
self.sd = self.xmpp.plugin['xep_0030']
def post_init(self):
base.base_plugin.post_init(self)
self.sd.add_feature('http://jabber.org/protocol/commands')
def addCommand(self, node, name, form, pointer=None, multi=False):
self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node)
self.sd.add_identity('automation', 'command-node', name, node)
self.sd.add_feature('http://jabber.org/protocol/commands', node)
self.sd.add_feature('jabber:x:data', node)
self.commands[node] = (name, form, pointer, multi)
def getNewSession(self):
return str(time.time()) + '-' + self.xmpp.getNewId()
def handler_command(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
node = in_command.get('node')
sessionid = self.getNewSession()
name, form, pointer, multi = self.commands[node]
self.sessions[sessionid] = {}
self.sessions[sessionid]['jid'] = xml.get('from')
self.sessions[sessionid]['to'] = xml.get('to')
self.sessions[sessionid]['past'] = [(form, None)]
self.sessions[sessionid]['next'] = pointer
npointer = pointer
if multi:
actions = ['next']
status = 'executing'
else:
if pointer is None:
status = 'completed'
actions = []
else:
status = 'executing'
actions = ['complete']
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
def handler_command_complete(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['old_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x'))
pointer(results,sessionid)
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
del self.sessions[in_command.get('sessionid')]
def handler_command_next(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['old_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x'))
form, npointer, next = pointer(results,sessionid)
self.sessions[sessionid]['next'] = npointer
self.sessions[sessionid]['past'].append((form, pointer))
actions = []
actions.append('prev')
if npointer is None:
status = 'completed'
else:
status = 'executing'
if next:
actions.append('next')
else:
actions.append('complete')
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
def handler_command_cancel(self, xml):
command = xml.find('{http://jabber.org/protocol/commands}command')
try:
del self.sessions[command.get('sessionid')]
except:
pass
self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled'))
def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]):
if not id:
id = self.xmpp.getNewId()
iq = self.xmpp.makeIqResult(id)
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['to'] = to
command = ET.Element('{http://jabber.org/protocol/commands}command')
command.attrib['node'] = node
command.attrib['status'] = status
xmlactions = ET.Element('actions')
for action in actions:
xmlactions.append(ET.Element(action))
if xmlactions:
command.append(xmlactions)
if not sessionid:
sessionid = self.getNewSession()
else:
iq.attrib['from'] = self.sessions[sessionid]['to']
command.attrib['sessionid'] = sessionid
if form is not None:
if hasattr(form,'getXML'):
form = form.getXML()
command.append(form)
iq.append(command)
return iq

View File

@@ -0,0 +1,313 @@
from __future__ import with_statement
from . import base
import logging
#from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
from . import stanza_pubsub
from . xep_0004 import Form
log = logging.getLogger(__name__)
class xep_0060(base.base_plugin):
"""
XEP-0060 Publish Subscribe
"""
def plugin_init(self):
self.xep = '0060'
self.description = 'Publish-Subscribe'
def create_node(self, jid, node, config=None, collection=False, ntype=None):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
create = ET.Element('create')
create.set('node', node)
pubsub.append(create)
configure = ET.Element('configure')
if collection:
ntype = 'collection'
#if config is None:
# submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
#else:
if config is not None:
submitform = config
if 'FORM_TYPE' in submitform.field:
submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
else:
submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
if ntype:
if 'pubsub#node_type' in submitform.field:
submitform.field['pubsub#node_type'].setValue(ntype)
else:
submitform.addField('pubsub#node_type', value=ntype)
else:
if 'pubsub#node_type' in submitform.field:
submitform.field['pubsub#node_type'].setValue('leaf')
else:
submitform.addField('pubsub#node_type', value='leaf')
submitform['type'] = 'submit'
configure.append(submitform.xml)
pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
def subscribe(self, jid, node, bare=True, subscribee=None):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
subscribe = ET.Element('subscribe')
subscribe.attrib['node'] = node
if subscribee is None:
if bare:
subscribe.attrib['jid'] = self.xmpp.boundjid.bare
else:
subscribe.attrib['jid'] = self.xmpp.boundjid.full
else:
subscribe.attrib['jid'] = subscribee
pubsub.append(subscribe)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
def unsubscribe(self, jid, node, bare=True, subscribee=None):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
unsubscribe = ET.Element('unsubscribe')
unsubscribe.attrib['node'] = node
if subscribee is None:
if bare:
unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
else:
unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
else:
unsubscribe.attrib['jid'] = subscribee
pubsub.append(unsubscribe)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
def getNodeConfig(self, jid, node=None): # if no node, then grab default
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
if node is not None:
configure = ET.Element('configure')
configure.attrib['node'] = node
else:
configure = ET.Element('default')
pubsub.append(configure)
#TODO: Add configure support.
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
return False
if node is not None:
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
else:
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
if not form or form is None:
log.error("No form found.")
return False
return Form(xml=form)
def getNodeSubscriptions(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
subscriptions = ET.Element('subscriptions')
subscriptions.attrib['node'] = node
pubsub.append(subscriptions)
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
return False
else:
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
if results is None:
return False
subs = {}
for sub in results:
subs[sub.get('jid')] = sub.get('subscription')
return subs
def getNodeAffiliations(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
affiliations = ET.Element('affiliations')
affiliations.attrib['node'] = node
pubsub.append(affiliations)
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
return False
else:
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
if results is None:
return False
subs = {}
for sub in results:
subs[sub.get('jid')] = sub.get('affiliation')
return subs
def deleteNode(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
iq = self.xmpp.makeIqSet()
delete = ET.Element('delete')
delete.attrib['node'] = node
pubsub.append(delete)
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
result = iq.send()
if result is not None and result is not False and result['type'] != 'error':
return True
else:
return False
def setNodeConfig(self, jid, node, config):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
configure = ET.Element('configure')
configure.attrib['node'] = node
config = config.getXML('submit')
configure.append(config)
pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result['type'] == 'error':
return False
return True
def setItem(self, jid, node, items=[]):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
publish = ET.Element('publish')
publish.attrib['node'] = node
for pub_item in items:
id, payload = pub_item
item = ET.Element('item')
if id is not None:
item.attrib['id'] = id
item.append(payload)
publish.append(item)
pubsub.append(publish)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error': return False
return True
def addItem(self, jid, node, items=[]):
return self.setItem(jid, node, items)
def deleteItem(self, jid, node, item):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
retract = ET.Element('retract')
retract.attrib['node'] = node
itemn = ET.Element('item')
itemn.attrib['id'] = item
retract.append(itemn)
pubsub.append(retract)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error': return False
return True
def getNodes(self, jid):
response = self.xmpp.plugin['xep_0030'].getItems(jid)
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
nodes = {}
if items is not None and items is not False:
for item in items:
nodes[item.get('node')] = item.get('name')
return nodes
def getItems(self, jid, node):
response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
nodeitems = []
if items is not None and items is not False:
for item in items:
nodeitems.append(item.get('node'))
return nodeitems
def addNodeToCollection(self, jid, child, parent=''):
config = self.getNodeConfig(jid, child)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
log.warning("pubsub#collection doesn't exist in config, trying to add it")
config.addField('pubsub#collection', value=parent)
if not self.setNodeConfig(jid, child, config):
return False
return True
def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
raise TypeError
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
affs = ET.Element('affiliations')
affs.attrib['node'] = node
aff = ET.Element('affiliation')
aff.attrib['jid'] = user_jid
aff.attrib['affiliation'] = affiliation
affs.append(aff)
pubsub.append(affs)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = ps_jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error':
return False
return True
def addNodeToCollection(self, jid, child, parent=''):
config = self.getNodeConfig(jid, child)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
log.warning("pubsub#collection doesn't exist in config, trying to add it")
config.addField('pubsub#collection', value=parent)
if not self.setNodeConfig(jid, child, config):
return False
return True
def removeNodeFromCollection(self, jid, child):
self.addNodeToCollection(jid, child, '')

View File

@@ -41,11 +41,10 @@ class FormField(ElementBase):
self._type = value self._type = value
def add_option(self, label='', value=''): def add_option(self, label='', value=''):
if self._type is None or self._type in self.option_types: if self._type in self.option_types:
opt = FieldOption() opt = FieldOption(parent=self)
opt['label'] = label opt['label'] = label
opt['value'] = value opt['value'] = value
self.append(opt)
else: else:
raise ValueError("Cannot add options to " + \ raise ValueError("Cannot add options to " + \
"a %s field." % self['type']) "a %s field." % self['type'])

View File

@@ -65,7 +65,7 @@ class Form(ElementBase):
if kwtype is None: if kwtype is None:
kwtype = ftype kwtype = ftype
field = FormField() field = FormField(parent=self)
field['var'] = var field['var'] = var
field['type'] = kwtype field['type'] = kwtype
field['value'] = value field['value'] = value
@@ -77,7 +77,6 @@ class Form(ElementBase):
field['options'] = options field['options'] = options
else: else:
del field['type'] del field['type']
self.append(field)
return field return field
def getXML(self, type='submit'): def getXML(self, type='submit'):
@@ -145,9 +144,10 @@ class Form(ElementBase):
def get_fields(self, use_dict=False): def get_fields(self, use_dict=False):
fields = OrderedDict() fields = OrderedDict()
for stanza in self['substanzas']: fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
if isinstance(stanza, FormField): for fieldXML in fieldsXML:
fields[stanza['var']] = stanza field = FormField(xml=fieldXML)
fields[field['var']] = field
return fields return fields
def get_instructions(self): def get_instructions(self):
@@ -221,8 +221,6 @@ class Form(ElementBase):
def set_values(self, values): def set_values(self, values):
fields = self['fields'] fields = self['fields']
for field in values: for field in values:
if field not in fields:
fields[field] = self.add_field(var=field)
fields[field]['value'] = values[field] fields[field]['value'] = values[field]
def merge(self, other): def merge(self, other):

View File

@@ -32,15 +32,15 @@ class XEP_0009(BasePlugin):
register_stanza_plugin(RPCQuery, MethodCall) register_stanza_plugin(RPCQuery, MethodCall)
register_stanza_plugin(RPCQuery, MethodResponse) register_stanza_plugin(RPCQuery, MethodResponse)
self.xmpp.register_handler( self.xmpp.registerHandler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
self._handle_method_call) self._handle_method_call)
) )
self.xmpp.register_handler( self.xmpp.registerHandler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
self._handle_method_response) self._handle_method_response)
) )
self.xmpp.register_handler( self.xmpp.registerHandler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)), Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
self._handle_error) self._handle_error)
) )

View File

@@ -1,16 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0020 import stanza
from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation
from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020
register_plugin(XEP_0020)

View File

@@ -1,36 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq, Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation
from sleekxmpp.plugins.xep_0004 import Form
log = logging.getLogger(__name__)
class XEP_0020(BasePlugin):
name = 'xep_0020'
description = 'XEP-0020: Feature Negotiation'
dependencies = set(['xep_0004', 'xep_0030'])
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace)
register_stanza_plugin(FeatureNegotiation, Form)
register_stanza_plugin(Iq, FeatureNegotiation)
register_stanza_plugin(Message, FeatureNegotiation)

View File

@@ -1,17 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class FeatureNegotiation(ElementBase):
name = 'feature'
namespace = 'http://jabber.org/protocol/feature-neg'
plugin_attrib = 'feature_neg'
interfaces = set()

View File

@@ -24,7 +24,7 @@ def _extract_data(data, kind):
if not begin_headers and 'BEGIN PGP %s' % kind in line: if not begin_headers and 'BEGIN PGP %s' % kind in line:
begin_headers = True begin_headers = True
continue continue
if begin_headers and line.strip() == '': if begin_headers and line.stripped() == '':
begin_data = True begin_data = True
continue continue
if 'END PGP %s' % kind in line: if 'END PGP %s' % kind in line:

View File

@@ -39,7 +39,7 @@ class Encrypted(ElementBase):
def set_encrypted(self, value): def set_encrypted(self, value):
parent = self.parent() parent = self.parent()
xmpp = parent.stream xmpp = parent.stream
data = xmpp['xep_0027'].encrypt(value, parent['to']) data = xmpp['xep_0027'].encrypt(value, parent['to'].bare)
if data: if data:
self.xml.text = data self.xml.text = data
else: else:

View File

@@ -128,10 +128,9 @@ class DiscoItems(ElementBase):
def del_items(self): def del_items(self):
"""Remove all items.""" """Remove all items."""
self._items = set() self._items = set()
items = [i for i in self.iterables if isinstance(i, DiscoItem)] for item in self['substanzas']:
for item in items: if isinstance(item, DiscoItem):
self.xml.remove(item.xml) self.xml.remove(item.xml)
self.iterables.remove(item)
class DiscoItem(ElementBase): class DiscoItem(ElementBase):

View File

@@ -125,12 +125,11 @@ class XEP_0045(BasePlugin):
self.xep = '0045' self.xep = '0045'
# load MUC support in presence stanzas # load MUC support in presence stanzas
register_stanza_plugin(Presence, MUCPresence) register_stanza_plugin(Presence, MUCPresence)
self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), self.handle_groupchat_error_message)) self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject)) self.xmpp.registerHandler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change))
self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change)) self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
self.xmpp.default_ns, self.xmpp.default_ns,
'http://jabber.org/protocol/muc#user', 'http://jabber.org/protocol/muc#user',
'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite)) 'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite))
@@ -180,14 +179,6 @@ class XEP_0045(BasePlugin):
self.xmpp.event('groupchat_message', msg) self.xmpp.event('groupchat_message', msg)
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
def handle_groupchat_error_message(self, msg):
""" Handle a message error event in a muc.
"""
self.xmpp.event('groupchat_message_error', msg)
self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg)
def handle_groupchat_subject(self, msg): def handle_groupchat_subject(self, msg):
""" Handle a message coming from a muc indicating """ Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room) a change of subject (or announcing it when joining the room)
@@ -207,9 +198,30 @@ class XEP_0045(BasePlugin):
if entry is not None and entry['jid'].full == jid: if entry is not None and entry['jid'].full == jid:
return nick return nick
def getRoomForm(self, room, ifrom=None):
iq = self.xmpp.makeIqGet()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
iq.append(query)
# For now, swallow errors to preserve existing API
try:
result = iq.send()
except IqError:
return False
except IqTimeout:
return False
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if xform is None: return False
form = self.xmpp.plugin['old_0004'].buildForm(xform)
return form
def configureRoom(self, room, form=None, ifrom=None): def configureRoom(self, room, form=None, ifrom=None):
if form is None: if form is None:
form = self.getRoomConfig(room, ifrom=ifrom) form = self.getRoomForm(room, ifrom=ifrom)
#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
iq = self.xmpp.makeIqSet() iq = self.xmpp.makeIqSet()
iq['to'] = room iq['to'] = room
if ifrom is not None: if ifrom is not None:
@@ -298,24 +310,6 @@ class XEP_0045(BasePlugin):
return False return False
return True return True
def setRole(self, room, nick, role):
""" Change role property of a nick in a room.
Typically, roles are temporary (they last only as long as you are in the
room), whereas affiliations are permanent (they last across groupchat
sessions).
"""
if role not in ('moderator', 'participant', 'visitor', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
item = ET.Element('item', {'role':role, 'nick':nick})
query.append(item)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
result = iq.send()
if result is False or result['type'] != 'result':
raise ValueError
return True
def invite(self, room, jid, reason='', mfrom=''): def invite(self, room, jid, reason='', mfrom=''):
""" Invite a jid to a room.""" """ Invite a jid to a room."""
msg = self.xmpp.makeMessage(room) msg = self.xmpp.makeMessage(room)

View File

@@ -21,25 +21,21 @@ class XEP_0047(BasePlugin):
dependencies = set(['xep_0030']) dependencies = set(['xep_0030'])
stanza = stanza stanza = stanza
default_config = { default_config = {
'block_size': 4096,
'max_block_size': 8192, 'max_block_size': 8192,
'window_size': 1, 'window_size': 1,
'auto_accept': False, 'auto_accept': True,
'accept_stream': None
} }
def plugin_init(self): def plugin_init(self):
self._streams = {} self.streams = {}
self._pending_streams = {} self.pending_streams = {}
self._pending_lock = threading.Lock() self.pending_close_streams = {}
self._stream_lock = threading.Lock() self._stream_lock = threading.Lock()
self._preauthed_sids_lock = threading.Lock()
self._preauthed_sids = {}
register_stanza_plugin(Iq, Open) register_stanza_plugin(Iq, Open)
register_stanza_plugin(Iq, Close) register_stanza_plugin(Iq, Close)
register_stanza_plugin(Iq, Data) register_stanza_plugin(Iq, Data)
register_stanza_plugin(Message, Data)
self.xmpp.register_handler(Callback( self.xmpp.register_handler(Callback(
'IBB Open', 'IBB Open',
@@ -56,71 +52,27 @@ class XEP_0047(BasePlugin):
StanzaPath('iq@type=set/ibb_data'), StanzaPath('iq@type=set/ibb_data'),
self._handle_data)) self._handle_data))
self.xmpp.register_handler(Callback(
'IBB Message Data',
StanzaPath('message/ibb_data'),
self._handle_data))
self.api.register(self._authorized, 'authorized', default=True)
self.api.register(self._authorized_sid, 'authorized_sid', default=True)
self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
self.api.register(self._get_stream, 'get_stream', default=True)
self.api.register(self._set_stream, 'set_stream', default=True)
self.api.register(self._del_stream, 'del_stream', default=True)
def plugin_end(self): def plugin_end(self):
self.xmpp.remove_handler('IBB Open') self.xmpp.remove_handler('IBB Open')
self.xmpp.remove_handler('IBB Close') self.xmpp.remove_handler('IBB Close')
self.xmpp.remove_handler('IBB Data') self.xmpp.remove_handler('IBB Data')
self.xmpp.remove_handler('IBB Message Data')
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
def session_bind(self, jid): def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
def _get_stream(self, jid, sid, peer_jid, data):
return self._streams.get((jid, sid, peer_jid), None)
def _set_stream(self, jid, sid, peer_jid, stream):
self._streams[(jid, sid, peer_jid)] = stream
def _del_stream(self, jid, sid, peer_jid, data):
with self._stream_lock:
if (jid, sid, peer_jid) in self._streams:
del self._streams[(jid, sid, peer_jid)]
def _accept_stream(self, iq): def _accept_stream(self, iq):
receiver = iq['to'] if self.accept_stream is not None:
sender = iq['from'] return self.accept_stream(iq)
sid = iq['ibb_open']['sid']
if self.api['authorized_sid'](receiver, sid, sender, iq):
return True
return self.api['authorized'](receiver, sid, sender, iq)
def _authorized(self, jid, sid, ifrom, iq):
if self.auto_accept: if self.auto_accept:
if iq['ibb_open']['block_size'] <= self.max_block_size: if iq['ibb_open']['block_size'] <= self.max_block_size:
return True return True
return False return False
def _authorized_sid(self, jid, sid, ifrom, iq): def open_stream(self, jid, block_size=4096, sid=None, window=1,
with self._preauthed_sids_lock:
if (jid, sid, ifrom) in self._preauthed_sids:
del self._preauthed_sids[(jid, sid, ifrom)]
return True
return False
def _preauthorize_sid(self, jid, sid, ifrom, data):
with self._preauthed_sids_lock:
self._preauthed_sids[(jid, sid, ifrom)] = True
def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False,
ifrom=None, block=True, timeout=None, callback=None): ifrom=None, block=True, timeout=None, callback=None):
if sid is None: if sid is None:
sid = str(uuid.uuid4()) sid = str(uuid.uuid4())
if block_size is None:
block_size = self.block_size
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'set' iq['type'] = 'set'
@@ -131,13 +83,12 @@ class XEP_0047(BasePlugin):
iq['ibb_open']['stanza'] = 'iq' iq['ibb_open']['stanza'] = 'iq'
stream = IBBytestream(self.xmpp, sid, block_size, stream = IBBytestream(self.xmpp, sid, block_size,
iq['from'], iq['to'], window, iq['to'], iq['from'], window)
use_messages)
with self._stream_lock: with self._stream_lock:
self._pending_streams[iq['id']] = stream self.pending_streams[iq['id']] = stream
self._pending_streams[iq['id']] = stream self.pending_streams[iq['id']] = stream
if block: if block:
resp = iq.send(timeout=timeout) resp = iq.send(timeout=timeout)
@@ -157,59 +108,49 @@ class XEP_0047(BasePlugin):
def _handle_opened_stream(self, iq): def _handle_opened_stream(self, iq):
if iq['type'] == 'result': if iq['type'] == 'result':
with self._stream_lock: with self._stream_lock:
stream = self._pending_streams.get(iq['id'], None) stream = self.pending_streams.get(iq['id'], None)
if stream is not None: if stream is not None:
log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from']) stream.sender = iq['to']
stream.self_jid = iq['to'] stream.receiver = iq['from']
stream.peer_jid = iq['from'] stream.stream_started.set()
stream.stream_started.set() self.streams[stream.sid] = stream
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) self.xmpp.event('ibb_stream_start', stream)
self.xmpp.event('ibb_stream_start', stream)
self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream)
with self._stream_lock: with self._stream_lock:
if iq['id'] in self._pending_streams: if iq['id'] in self.pending_streams:
del self._pending_streams[iq['id']] del self.pending_streams[iq['id']]
def _handle_open_request(self, iq): def _handle_open_request(self, iq):
sid = iq['ibb_open']['sid'] sid = iq['ibb_open']['sid']
size = iq['ibb_open']['block_size'] or self.block_size size = iq['ibb_open']['block_size']
log.debug('Received IBB stream request from %s', iq['from'])
if not sid:
raise XMPPError(etype='modify', condition='bad-request')
if not self._accept_stream(iq): if not self._accept_stream(iq):
raise XMPPError(etype='modify', condition='not-acceptable') raise XMPPError('not-acceptable')
if size > self.max_block_size: if size > self.max_block_size:
raise XMPPError('resource-constraint') raise XMPPError('resource-constraint')
stream = IBBytestream(self.xmpp, sid, size, stream = IBBytestream(self.xmpp, sid, size,
iq['to'], iq['from'], iq['from'], iq['to'],
self.window_size) self.window_size)
stream.stream_started.set() stream.stream_started.set()
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) self.streams[sid] = stream
iq.reply() iq.reply()
iq.send() iq.send()
self.xmpp.event('ibb_stream_start', stream) self.xmpp.event('ibb_stream_start', stream)
self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream)
def _handle_data(self, stanza): def _handle_data(self, iq):
sid = stanza['ibb_data']['sid'] sid = iq['ibb_data']['sid']
stream = self.api['get_stream'](stanza['to'], sid, stanza['from']) stream = self.streams.get(sid, None)
if stream is not None and stanza['from'] == stream.peer_jid: if stream is not None and iq['from'] != stream.sender:
stream._recv_data(stanza) stream._recv_data(iq)
else: else:
raise XMPPError('item-not-found') raise XMPPError('item-not-found')
def _handle_close(self, iq): def _handle_close(self, iq):
sid = iq['ibb_close']['sid'] sid = iq['ibb_close']['sid']
stream = self.api['get_stream'](iq['to'], sid, iq['from']) stream = self.streams.get(sid, None)
if stream is not None and iq['from'] == stream.peer_jid: if stream is not None and iq['from'] != stream.sender:
stream._closed(iq) stream._closed(iq)
self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid)
else: else:
raise XMPPError('item-not-found') raise XMPPError('item-not-found')

View File

@@ -2,7 +2,6 @@ import socket
import threading import threading
import logging import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.util import Queue from sleekxmpp.util import Queue
from sleekxmpp.exceptions import XMPPError from sleekxmpp.exceptions import XMPPError
@@ -12,17 +11,14 @@ log = logging.getLogger(__name__)
class IBBytestream(object): class IBBytestream(object):
def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False): def __init__(self, xmpp, sid, block_size, to, ifrom, window_size=1):
self.xmpp = xmpp self.xmpp = xmpp
self.sid = sid self.sid = sid
self.block_size = block_size self.block_size = block_size
self.window_size = window_size self.window_size = window_size
self.use_messages = use_messages
if jid is None: self.receiver = to
jid = xmpp.boundjid self.sender = ifrom
self.self_jid = jid
self.peer_jid = peer
self.send_seq = -1 self.send_seq = -1
self.recv_seq = -1 self.recv_seq = -1
@@ -50,27 +46,16 @@ class IBBytestream(object):
with self._send_seq_lock: with self._send_seq_lock:
self.send_seq = (self.send_seq + 1) % 65535 self.send_seq = (self.send_seq + 1) % 65535
seq = self.send_seq seq = self.send_seq
if self.use_messages: iq = self.xmpp.Iq()
msg = self.xmpp.Message() iq['type'] = 'set'
msg['to'] = self.peer_jid iq['to'] = self.receiver
msg['from'] = self.self_jid iq['from'] = self.sender
msg['id'] = self.xmpp.new_id() iq['ibb_data']['sid'] = self.sid
msg['ibb_data']['sid'] = self.sid iq['ibb_data']['seq'] = seq
msg['ibb_data']['seq'] = seq iq['ibb_data']['data'] = data
msg['ibb_data']['data'] = data self.window_empty.clear()
msg.send() self.window_ids.add(iq['id'])
self.send_window.release() iq.send(block=False, callback=self._recv_ack)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = self.peer_jid
iq['from'] = self.self_jid
iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data
self.window_empty.clear()
self.window_ids.add(iq['id'])
iq.send(block=False, callback=self._recv_ack)
return len(data) return len(data)
def sendall(self, data): def sendall(self, data):
@@ -86,25 +71,23 @@ class IBBytestream(object):
if iq['type'] == 'error': if iq['type'] == 'error':
self.close() self.close()
def _recv_data(self, stanza): def _recv_data(self, iq):
with self._recv_seq_lock: with self._recv_seq_lock:
new_seq = stanza['ibb_data']['seq'] new_seq = iq['ibb_data']['seq']
if new_seq != (self.recv_seq + 1) % 65535: if new_seq != (self.recv_seq + 1) % 65535:
self.close() self.close()
raise XMPPError('unexpected-request') raise XMPPError('unexpected-request')
self.recv_seq = new_seq self.recv_seq = new_seq
data = stanza['ibb_data']['data'] data = iq['ibb_data']['data']
if len(data) > self.block_size: if len(data) > self.block_size:
self.close() self.close()
raise XMPPError('not-acceptable') raise XMPPError('not-acceptable')
self.recv_queue.put(data) self.recv_queue.put(data)
self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data}) self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data})
iq.reply()
if isinstance(stanza, Iq): iq.send()
stanza.reply()
stanza.send()
def recv(self, *args, **kwargs): def recv(self, *args, **kwargs):
return self.read(block=True) return self.read(block=True)
@@ -123,8 +106,8 @@ class IBBytestream(object):
def close(self): def close(self):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'set' iq['type'] = 'set'
iq['to'] = self.peer_jid iq['to'] = self.receiver
iq['from'] = self.self_jid iq['from'] = self.sender
iq['ibb_close']['sid'] = self.sid iq['ibb_close']['sid'] = self.sid
self.stream_out_closed.set() self.stream_out_closed.set()
iq.send(block=False, iq.send(block=False,
@@ -134,6 +117,9 @@ class IBBytestream(object):
def _closed(self, iq): def _closed(self, iq):
self.stream_in_closed.set() self.stream_in_closed.set()
self.stream_out_closed.set() self.stream_out_closed.set()
while not self.window_empty.is_set():
log.info('waiting for send window to empty')
self.window_empty.wait(timeout=1)
iq.reply() iq.reply()
iq.send() iq.send()
self.xmpp.event('ibb_stream_end', self) self.xmpp.event('ibb_stream_end', self)

View File

@@ -1,15 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0048.stanza import Bookmarks, Conference, URL
from sleekxmpp.plugins.xep_0048.bookmarks import XEP_0048
register_plugin(XEP_0048)

View File

@@ -1,76 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL
log = logging.getLogger(__name__)
class XEP_0048(BasePlugin):
name = 'xep_0048'
description = 'XEP-0048: Bookmarks'
dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'])
stanza = stanza
default_config = {
'auto_join': False,
'storage_method': 'xep_0049'
}
def plugin_init(self):
register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks)
self.xmpp['xep_0049'].register(Bookmarks)
self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks)
self.xmpp.add_event_handler('session_start', self._autojoin)
def plugin_end(self):
self.xmpp.del_event_handler('session_start', self._autojoin)
def _autojoin(self, __):
if not self.auto_join:
return
try:
result = self.get_bookmarks(method=self.storage_method)
except XMPPError:
return
if self.storage_method == 'xep_0223':
bookmarks = result['pubsub']['items']['item']['bookmarks']
else:
bookmarks = result['private']['bookmarks']
for conf in bookmarks['conferences']:
if conf['autojoin']:
log.debug('Auto joining %s as %s', conf['jid'], conf['nick'])
self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'],
password=conf['password'])
def set_bookmarks(self, bookmarks, method=None, **iqargs):
if not method:
method = self.storage_method
return self.xmpp[method].store(bookmarks, **iqargs)
def get_bookmarks(self, method=None, **iqargs):
if not method:
method = self.storage_method
loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks'
return self.xmpp[method].retrieve(loc, **iqargs)

View File

@@ -1,65 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class Bookmarks(ElementBase):
name = 'storage'
namespace = 'storage:bookmarks'
plugin_attrib = 'bookmarks'
interfaces = set()
def add_conference(self, jid, nick, name=None, autojoin=None, password=None):
conf = Conference()
conf['jid'] = jid
conf['nick'] = nick
if name is None:
name = jid
conf['name'] = name
conf['autojoin'] = autojoin
conf['password'] = password
self.append(conf)
def add_url(self, url, name=None):
saved_url = URL()
saved_url['url'] = url
if name is None:
name = url
saved_url['name'] = name
self.append(saved_url)
class Conference(ElementBase):
name = 'conference'
namespace = 'storage:bookmarks'
plugin_attrib = 'conference'
plugin_multi_attrib = 'conferences'
interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name'])
sub_interfaces = set(['nick', 'password'])
def get_autojoin(self):
value = self._get_attr('autojoin')
return value in ('1', 'true')
def set_autojoin(self, value):
del self['autojoin']
if value in ('1', 'true', True):
self._set_attr('autojoin', 'true')
class URL(ElementBase):
name = 'url'
namespace = 'storage:bookmarks'
plugin_attrib = 'url'
plugin_multi_attrib = 'urls'
interfaces = set(['url', 'name'])
register_stanza_plugin(Bookmarks, Conference, iterable=True)
register_stanza_plugin(Bookmarks, URL, iterable=True)

View File

@@ -267,50 +267,20 @@ class XEP_0050(BasePlugin):
iq -- The command continuation request. iq -- The command continuation request.
""" """
sessionid = iq['command']['sessionid'] sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid) session = self.sessions[sessionid]
if session: handler = session['next']
handler = session['next'] interfaces = session['interfaces']
interfaces = session['interfaces'] results = []
results = [] for stanza in iq['command']['substanzas']:
for stanza in iq['command']['substanzas']: if stanza.plugin_attrib in interfaces:
if stanza.plugin_attrib in interfaces: results.append(stanza)
results.append(stanza) if len(results) == 1:
if len(results) == 1: results = results[0]
results = results[0]
session = handler(results, session) session = handler(results, session)
self._process_command_response(iq, session) self._process_command_response(iq, session)
else:
raise XMPPError('item-not-found')
def _handle_command_prev(self, iq):
"""
Process a request for the prev step in the workflow
for a command with multiple steps.
Arguments:
iq -- The command continuation request.
"""
sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid)
if session:
handler = session['prev']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
session = handler(results, session)
self._process_command_response(iq, session)
else:
raise XMPPError('item-not-found')
def _process_command_response(self, iq, session): def _process_command_response(self, iq, session):
""" """
@@ -378,23 +348,23 @@ class XEP_0050(BasePlugin):
""" """
node = iq['command']['node'] node = iq['command']['node']
sessionid = iq['command']['sessionid'] sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
handler = session['cancel']
session = self.sessions.get(sessionid) if handler:
handler(iq, session)
if session: try:
handler = session['cancel']
if handler:
handler(iq, session)
del self.sessions[sessionid] del self.sessions[sessionid]
iq.reply() except:
iq['command']['node'] = node pass
iq['command']['sessionid'] = sessionid
iq['command']['status'] = 'canceled'
iq['command']['notes'] = session['notes']
iq.send()
else:
raise XMPPError('item-not-found')
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['status'] = 'canceled'
iq['command']['notes'] = session['notes']
iq.send()
def _handle_command_complete(self, iq): def _handle_command_complete(self, iq):
""" """
@@ -408,32 +378,28 @@ class XEP_0050(BasePlugin):
""" """
node = iq['command']['node'] node = iq['command']['node']
sessionid = iq['command']['sessionid'] sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid) session = self.sessions[sessionid]
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
if session: if handler:
handler = session['next'] handler(results, session)
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
if handler: iq.reply()
handler(results, session) iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
iq['command']['notes'] = session['notes']
iq.send()
del self.sessions[sessionid] del self.sessions[sessionid]
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
iq['command']['notes'] = session['notes']
iq.send()
else:
raise XMPPError('item-not-found')
# ================================================================= # =================================================================
# Client side (command user) API # Client side (command user) API
@@ -571,7 +537,7 @@ class XEP_0050(BasePlugin):
else: else:
iq.send(block=False, callback=self._handle_command_result) iq.send(block=False, callback=self._handle_command_result)
def continue_command(self, session, direction='next'): def continue_command(self, session):
""" """
Execute the next action of the command. Execute the next action of the command.
@@ -585,7 +551,7 @@ class XEP_0050(BasePlugin):
self.send_command(session['jid'], self.send_command(session['jid'],
session['node'], session['node'],
ifrom=session.get('from', None), ifrom=session.get('from', None),
action=direction, action='next',
payload=session.get('payload', None), payload=session.get('payload', None),
sessionid=session['id'], sessionid=session['id'],
flow=True, flow=True,

View File

@@ -8,7 +8,7 @@
import logging import logging
from sleekxmpp import JID, Iq from sleekxmpp import Iq
from sleekxmpp.exceptions import XMPPError from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.handler import Callback
@@ -59,20 +59,10 @@ class XEP_0054(BasePlugin):
def make_vcard(self): def make_vcard(self):
return VCardTemp() return VCardTemp()
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False, def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
block=True, callback=None, timeout=None): block=True, callback=None, timeout=None):
if local is None: if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
if jid is not None and not isinstance(jid, JID): local = True
jid = JID(jid)
if self.xmpp.is_component:
if jid.domain == self.xmpp.boundjid.domain:
local = True
else:
if str(jid) == str(self.xmpp.boundjid):
local = True
jid = jid.full
elif jid in (None, ''):
local = True
if local: if local:
vcard = self.api['get_vcard'](jid, None, ifrom) vcard = self.api['get_vcard'](jid, None, ifrom)

View File

@@ -423,7 +423,7 @@ class XEP_0060(BasePlugin):
callback=None, timeout=None): callback=None, timeout=None):
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
iq['pubsub_owner']['configure']['node'] = node iq['pubsub_owner']['configure']['node'] = node
iq['pubsub_owner']['configure'].append(config) iq['pubsub_owner']['configure']['form'].values = config.values
return iq.send(block=block, callback=callback, timeout=timeout) return iq.send(block=block, callback=callback, timeout=timeout)
def publish(self, jid, node, id=None, payload=None, options=None, def publish(self, jid, node, id=None, payload=None, options=None,

View File

@@ -74,12 +74,7 @@ class Item(ElementBase):
def set_payload(self, value): def set_payload(self, value):
del self['payload'] del self['payload']
if isinstance(value, ElementBase): self.append(value)
if value.tag_name() in self.plugin_tag_map:
self.init_plugin(value.plugin_attrib, existing_xml=value.xml)
self.xml.append(value.xml)
else:
self.xml.append(value)
def get_payload(self): def get_payload(self):
childs = list(self.xml) childs = list(self.xml)
@@ -248,6 +243,39 @@ class PublishOptions(ElementBase):
self.parent().xml.remove(self.xml) self.parent().xml.remove(self.xml)
class PubsubState(ElementBase):
"""This is an experimental pubsub extension."""
namespace = 'http://jabber.org/protocol/psstate'
name = 'state'
plugin_attrib = 'psstate'
interfaces = set(('node', 'item', 'payload'))
def set_payload(self, value):
self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml:
self.xml.remove(child)
class PubsubStateEvent(ElementBase):
"""This is an experimental pubsub extension."""
namespace = 'http://jabber.org/protocol/psstate#event'
name = 'event'
plugin_attrib = 'psstate_event'
intefaces = set(tuple())
register_stanza_plugin(Iq, PubsubState)
register_stanza_plugin(Message, PubsubStateEvent)
register_stanza_plugin(PubsubStateEvent, PubsubState)
register_stanza_plugin(Iq, Pubsub) register_stanza_plugin(Iq, Pubsub)
register_stanza_plugin(Pubsub, Affiliations) register_stanza_plugin(Pubsub, Affiliations)
register_stanza_plugin(Pubsub, Configure) register_stanza_plugin(Pubsub, Configure)

View File

@@ -34,8 +34,7 @@ class DefaultConfig(ElementBase):
return self['form'] return self['form']
def set_config(self, value): def set_config(self, value):
del self['from'] self['form'].values = value.values
self.append(value)
return self return self
@@ -94,9 +93,7 @@ class OwnerRedirect(ElementBase):
class OwnerSubscriptions(Subscriptions): class OwnerSubscriptions(Subscriptions):
name = 'subscriptions'
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
plugin_attrib = name
interfaces = set(('node',)) interfaces = set(('node',))
def append(self, subscription): def append(self, subscription):

View File

@@ -1,7 +0,0 @@
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0065.stanza import Socks5
from sleekxmpp.plugins.xep_0065.proxy import XEP_0065
register_plugin(XEP_0065)

View File

@@ -1,292 +0,0 @@
import logging
import threading
import socket
from hashlib import sha1
from uuid import uuid4
from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
from sleekxmpp.stanza import Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0065 import stanza, Socks5
log = logging.getLogger(__name__)
class XEP_0065(base_plugin):
name = 'xep_0065'
description = "Socks5 Bytestreams"
dependencies = set(['xep_0030'])
default_config = {
'auto_accept': False
}
def plugin_init(self):
register_stanza_plugin(Iq, Socks5)
self._proxies = {}
self._sessions = {}
self._sessions_lock = threading.Lock()
self._preauthed_sids_lock = threading.Lock()
self._preauthed_sids = {}
self.xmpp.register_handler(
Callback('Socks5 Bytestreams',
StanzaPath('iq@type=set/socks/streamhost'),
self._handle_streamhost))
self.api.register(self._authorized, 'authorized', default=True)
self.api.register(self._authorized_sid, 'authorized_sid', default=True)
self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Socks5.namespace)
def plugin_end(self):
self.xmpp.remove_handler('Socks5 Bytestreams')
self.xmpp.remove_handler('Socks5 Streamhost Used')
self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace)
def get_socket(self, sid):
"""Returns the socket associated to the SID."""
return self._sessions.get(sid, None)
def handshake(self, to, ifrom=None, sid=None, timeout=None):
""" Starts the handshake to establish the socks5 bytestreams
connection.
"""
if not self._proxies:
self._proxies = self.discover_proxies()
if sid is None:
sid = uuid4().hex
used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
proxy = used['socks']['streamhost_used']['jid']
if proxy not in self._proxies:
log.warning('Received unknown SOCKS5 proxy: %s', proxy)
return
with self._sessions_lock:
self._sessions[sid] = self._connect_proxy(
sid,
self.xmpp.boundjid,
to,
self._proxies[proxy][0],
self._proxies[proxy][1],
peer=to)
# Request that the proxy activate the session with the target.
self.activate(proxy, sid, to, timeout=timeout)
socket = self.get_socket(sid)
self.xmpp.event('stream:%s:%s' % (sid, to), socket)
return socket
def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None):
if sid is None:
sid = uuid4().hex
# Requester initiates S5B negotiation with Target by sending
# IQ-set that includes the JabberID and network address of
# StreamHost as well as the StreamID (SID) of the proposed
# bytestream.
iq = self.xmpp.Iq()
iq['to'] = to
iq['from'] = ifrom
iq['type'] = 'set'
iq['socks']['sid'] = sid
for proxy, (host, port) in self._proxies.items():
iq['socks'].add_streamhost(proxy, host, port)
return iq.send(block=block, timeout=timeout, callback=callback)
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
if jid is None:
if self.xmpp.is_component:
jid = self.xmpp.server
else:
jid = self.xmpp.boundjid.server
discovered = set()
disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
for item in disco_items['disco_items']['items']:
try:
disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout)
except XMPPError:
continue
else:
# Verify that the identity is a bytestream proxy.
identities = disco_info['disco_info']['identities']
for identity in identities:
if identity[0] == 'proxy' and identity[1] == 'bytestreams':
discovered.add(disco_info['from'])
for jid in discovered:
try:
addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
self._proxies[jid] = (addr['socks']['streamhost']['host'],
addr['socks']['streamhost']['port'])
except XMPPError:
continue
return self._proxies
def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None):
"""Get the network information of a proxy."""
iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom)
iq.enable('socks')
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_streamhost(self, iq):
"""Handle incoming SOCKS5 session request."""
sid = iq['socks']['sid']
if not sid:
raise XMPPError(etype='modify', condition='bad-request')
if not self._accept_stream(iq):
raise XMPPError(etype='modify', condition='not-acceptable')
streamhosts = iq['socks']['streamhosts']
conn = None
used_streamhost = None
sender = iq['from']
for streamhost in streamhosts:
try:
conn = self._connect_proxy(sid,
sender,
self.xmpp.boundjid,
streamhost['host'],
streamhost['port'],
peer=sender)
used_streamhost = streamhost['jid']
break
except socket.error:
continue
else:
raise XMPPError(etype='cancel', condition='item-not-found')
iq.reply()
with self._sessions_lock:
self._sessions[sid] = conn
iq['socks']['sid'] = sid
iq['socks']['streamhost_used']['jid'] = used_streamhost
iq.send()
self.xmpp.event('socks5_stream', conn)
self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None):
"""Activate the socks5 session that has been negotiated."""
iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom)
iq['socks']['sid'] = sid
iq['socks']['activate'] = target
iq.send(block=block, timeout=timeout, callback=callback)
def deactivate(self, sid):
"""Closes the proxy socket associated with this SID."""
sock = self._sessions.get(sid)
if sock:
try:
# sock.close() will also delete sid from self._sessions (see _connect_proxy)
sock.close()
except socket.error:
pass
# Though this should not be neccessary remove the closed session anyway
with self._sessions_lock:
if sid in self._sessions:
log.warn(('SOCKS5 session with sid = "%s" was not ' +
'removed from _sessions by sock.close()') % sid)
del self._sessions[sid]
def close(self):
"""Closes all proxy sockets."""
for sid, sock in self._sessions.items():
sock.close()
with self._sessions_lock:
self._sessions = {}
def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
""" Establishes a connection between the client and the server-side
Socks5 proxy.
sid : The StreamID. <str>
requester : The JID of the requester. <str>
target : The JID of the target. <str>
proxy_host : The hostname or the IP of the proxy. <str>
proxy_port : The port of the proxy. <str> or <int>
peer : The JID for the other side of the stream, regardless
of target or requester status.
"""
# Because the xep_0065 plugin uses the proxy_port as string,
# the Proxy class accepts the proxy_port argument as a string
# or an integer. Here, we force to use the port as an integer.
proxy_port = int(proxy_port)
sock = socksocket()
sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port)
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
# where the output is hexadecimal-encoded (not binary).
digest = sha1()
digest.update(sid)
digest.update(str(requester))
digest.update(str(target))
dest = digest.hexdigest()
# The port MUST be 0.
sock.connect((dest, 0))
log.info('Socket connected.')
_close = sock.close
def close(*args, **kwargs):
with self._sessions_lock:
if sid in self._sessions:
del self._sessions[sid]
_close()
log.info('Socket closed.')
sock.close = close
sock.peer_jid = peer
sock.self_jid = target if requester == peer else requester
self.xmpp.event('socks_connected', sid)
return sock
def _accept_stream(self, iq):
receiver = iq['to']
sender = iq['from']
sid = iq['socks']['sid']
if self.api['authorized_sid'](receiver, sid, sender, iq):
return True
return self.api['authorized'](receiver, sid, sender, iq)
def _authorized(self, jid, sid, ifrom, iq):
return self.auto_accept
def _authorized_sid(self, jid, sid, ifrom, iq):
with self._preauthed_sids_lock:
log.debug('>>> authed sids: %s', self._preauthed_sids)
log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
if (jid, sid, ifrom) in self._preauthed_sids:
del self._preauthed_sids[(jid, sid, ifrom)]
return True
return False
def _preauthorize_sid(self, jid, sid, ifrom, data):
log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data)
with self._preauthed_sids_lock:
self._preauthed_sids[(jid, sid, ifrom)] = True

View File

@@ -1,47 +0,0 @@
from sleekxmpp.jid import JID
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class Socks5(ElementBase):
name = 'query'
namespace = 'http://jabber.org/protocol/bytestreams'
plugin_attrib = 'socks'
interfaces = set(['sid', 'activate'])
sub_interfaces = set(['activate'])
def add_streamhost(self, jid, host, port):
sh = StreamHost(parent=self)
sh['jid'] = jid
sh['host'] = host
sh['port'] = port
class StreamHost(ElementBase):
name = 'streamhost'
namespace = 'http://jabber.org/protocol/bytestreams'
plugin_attrib = 'streamhost'
plugin_multi_attrib = 'streamhosts'
interfaces = set(['host', 'jid', 'port'])
def set_jid(self, value):
return self._set_attr('jid', str(value))
def get_jid(self):
return JID(self._get_attr('jid'))
class StreamHostUsed(ElementBase):
name = 'streamhost-used'
namespace = 'http://jabber.org/protocol/bytestreams'
plugin_attrib = 'streamhost_used'
interfaces = set(['jid'])
def set_jid(self, value):
return self._set_attr('jid', str(value))
def get_jid(self):
return JID(self._get_attr('jid'))
register_stanza_plugin(Socks5, StreamHost, iterable=True)
register_stanza_plugin(Socks5, StreamHostUsed)

View File

@@ -1,15 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permissio
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0071.stanza import XHTML_IM
from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071
register_plugin(XEP_0071)

View File

@@ -1,81 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Message
from sleekxmpp.util import unicode
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring
XHTML_NS = 'http://www.w3.org/1999/xhtml'
class XHTML_IM(ElementBase):
namespace = 'http://jabber.org/protocol/xhtml-im'
name = 'html'
interfaces = set(['body'])
lang_interfaces = set(['body'])
plugin_attrib = name
def set_body(self, content, lang=None):
if lang is None:
lang = self.get_lang()
self.del_body(lang)
if lang == '*':
for sublang, subcontent in content.items():
self.set_body(subcontent, sublang)
else:
if isinstance(content, type(ET.Element('test'))):
content = unicode(ET.tostring(content))
else:
content = unicode(content)
header = '<body xmlns="%s"' % XHTML_NS
if lang:
header = '%s xml:lang="%s"' % (header, lang)
content = '%s>%s</body>' % (header, content)
xhtml = ET.fromstring(content)
self.xml.append(xhtml)
def get_body(self, lang=None):
"""Return the contents of the HTML body."""
if lang is None:
lang = self.get_lang()
bodies = self.xml.findall('{%s}body' % XHTML_NS)
if lang == '*':
result = OrderedDict()
for body in bodies:
body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '')
body_result = []
body_result.append(body.text if body.text else '')
for child in body:
body_result.append(tostring(child, xmlns=XHTML_NS))
body_result.append(body.tail if body.tail else '')
result[body_lang] = ''.join(body_result)
return result
else:
for body in bodies:
if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
result = []
result.append(body.text if body.text else '')
for child in body:
result.append(tostring(child, xmlns=XHTML_NS))
result.append(body.tail if body.tail else '')
return ''.join(result)
return ''
def del_body(self, lang=None):
if lang is None:
lang = self.get_lang()
bodies = self.xml.findall('{%s}body' % XHTML_NS)
for body in bodies:
if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
self.xml.remove(body)
return

View File

@@ -1,30 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM
class XEP_0071(BasePlugin):
name = 'xep_0071'
description = 'XEP-0071: XHTML-IM'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, XHTML_IM)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace)

View File

@@ -7,7 +7,6 @@
""" """
import logging import logging
import ssl
from sleekxmpp.stanza import StreamFeatures, Iq from sleekxmpp.stanza import StreamFeatures, Iq
from sleekxmpp.xmlstream import register_stanza_plugin, JID from sleekxmpp.xmlstream import register_stanza_plugin, JID
@@ -30,7 +29,6 @@ class XEP_0077(BasePlugin):
stanza = stanza stanza = stanza
default_config = { default_config = {
'create_account': True, 'create_account': True,
'force_registration': False,
'order': 50 'order': 50
} }
@@ -47,29 +45,10 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
self.xmpp.add_event_handler('connected', self._force_registration)
def plugin_end(self): def plugin_end(self):
if not self.xmpp.is_component: if not self.xmpp.is_component:
self.xmpp.unregister_feature('register', self.order) self.xmpp.unregister_feature('register', self.order)
def _force_registration(self, event):
if self.force_registration:
self.xmpp.add_filter('in', self._force_stream_feature)
def _force_stream_feature(self, stanza):
if isinstance(stanza, StreamFeatures):
if self.xmpp.use_tls or self.xmpp.use_ssl:
if 'starttls' not in self.xmpp.features:
return stanza
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
return stanza
if 'mechanisms' not in self.xmpp.features:
log.debug('Forced adding in-band registration stream feature')
stanza.enable('register')
self.xmpp.del_filter('in', self._force_stream_feature)
return stanza
def _handle_register_feature(self, features): def _handle_register_feature(self, features):
if 'mechanisms' in self.xmpp.features: if 'mechanisms' in self.xmpp.features:
# We have already logged in with an account # We have already logged in with an account

View File

@@ -6,7 +6,6 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import uuid
import logging import logging
import hashlib import hashlib
import random import random
@@ -99,7 +98,7 @@ class XEP_0078(BasePlugin):
# A resource is required, so create a random one if necessary # A resource is required, so create a random one if necessary
resource = self.xmpp.requested_jid.resource resource = self.xmpp.requested_jid.resource
if not resource: if not resource:
resource = str(uuid.uuid4()) resource = uuid.uuid4()
iq['auth']['resource'] = resource iq['auth']['resource'] = resource

View File

@@ -1,18 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0079.stanza import (
AMP, Rule, InvalidRules, UnsupportedConditions,
UnsupportedActions, FailedRules, FailedRule,
AMPFeature)
from sleekxmpp.plugins.xep_0079.amp import XEP_0079
register_plugin(XEP_0079)

View File

@@ -1,79 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permissio
"""
import logging
from sleekxmpp.stanza import Message, Error, StreamFeatures
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.matcher import StanzaPath, MatchMany
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0079 import stanza
log = logging.getLogger(__name__)
class XEP_0079(BasePlugin):
"""
XEP-0079 Advanced Message Processing
"""
name = 'xep_0079'
description = 'XEP-0079: Advanced Message Processing'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.AMP)
register_stanza_plugin(Error, stanza.InvalidRules)
register_stanza_plugin(Error, stanza.UnsupportedConditions)
register_stanza_plugin(Error, stanza.UnsupportedActions)
register_stanza_plugin(Error, stanza.FailedRules)
self.xmpp.register_handler(
Callback('AMP Response',
MatchMany([
StanzaPath('message/error/failed_rules'),
StanzaPath('message/amp')
]),
self._handle_amp_response))
if not self.xmpp.is_component:
self.xmpp.register_feature('amp',
self._handle_amp_feature,
restart=False,
order=9000)
register_stanza_plugin(StreamFeatures, stanza.AMPFeature)
def plugin_end(self):
self.xmpp.remove_handler('AMP Response')
def _handle_amp_response(self, msg):
log.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
if msg['type'] == 'error':
self.xmpp.event('amp_error', msg)
elif msg['amp']['status'] in ('alert', 'notify'):
self.xmpp.event('amp_%s' % msg['amp']['status'], msg)
def _handle_amp_feature(self, features):
log.debug('Advanced Message Processing is available.')
self.xmpp.features.add('amp')
def discover_support(self, jid=None, **iqargs):
if jid is None:
if self.xmpp.is_component:
jid = self.xmpp.server_host
else:
jid = self.xmpp.boundjid.host
return self.xmpp['xep_0030'].get_info(
jid=jid,
node='http://jabber.org/protocol/amp',
**iqargs)

View File

@@ -1,96 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import unicode_literals
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class AMP(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'amp'
plugin_attrib = 'amp'
interfaces = set(['from', 'to', 'status', 'per_hop'])
def get_from(self):
return JID(self._get_attr('from'))
def set_from(self, value):
return self._set_attr('from', str(value))
def get_to(self):
return JID(self._get_attr('from'))
def set_to(self, value):
return self._set_attr('to', str(value))
def get_per_hop(self):
return self._get_attr('per-hop') == 'true'
def set_per_hop(self, value):
if value:
return self._set_attr('per-hop', 'true')
else:
return self._del_attr('per-hop')
def del_per_hop(self):
return self._del_attr('per-hop')
def add_rule(self, action, condition, value):
rule = Rule(parent=self)
rule['action'] = action
rule['condition'] = condition
rule['value'] = value
class Rule(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'rule'
plugin_attrib = name
plugin_multi_attrib = 'rules'
interfaces = set(['action', 'condition', 'value'])
class InvalidRules(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'invalid-rules'
plugin_attrib = 'invalid_rules'
class UnsupportedConditions(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'unsupported-conditions'
plugin_attrib = 'unsupported_conditions'
class UnsupportedActions(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'unsupported-actions'
plugin_attrib = 'unsupported_actions'
class FailedRule(Rule):
namespace = 'http://jabber.org/protocol/amp#errors'
class FailedRules(ElementBase):
namespace = 'http://jabber.org/protocol/amp#errors'
name = 'failed-rules'
plugin_attrib = 'failed_rules'
class AMPFeature(ElementBase):
namespace = 'http://jabber.org/features/amp'
name = 'amp'
register_stanza_plugin(AMP, Rule, iterable=True)
register_stanza_plugin(InvalidRules, Rule, iterable=True)
register_stanza_plugin(UnsupportedConditions, Rule, iterable=True)
register_stanza_plugin(UnsupportedActions, Rule, iterable=True)
register_stanza_plugin(FailedRules, FailedRule, iterable=True)

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import logging
import datetime as dt import datetime as dt
from sleekxmpp.plugins import BasePlugin, register_plugin from sleekxmpp.plugins import BasePlugin, register_plugin

View File

@@ -82,7 +82,6 @@ class XEP_0084(BasePlugin):
metadata.add_pointer(pointer) metadata.add_pointer(pointer)
return self.xmpp['xep_0163'].publish(metadata, return self.xmpp['xep_0163'].publish(metadata,
id=info['id'],
ifrom=ifrom, ifrom=ifrom,
block=block, block=block,
callback=callback, callback=callback,

View File

@@ -21,15 +21,14 @@ class LegacyDelay(ElementBase):
interfaces = set(('from', 'stamp', 'text')) interfaces = set(('from', 'stamp', 'text'))
def get_from(self): def get_from(self):
from_ = self._get_attr('from') return JID(self._get_attr('from'))
return JID(from_) if from_ else None
def set_from(self, value): def set_from(self, value):
self._set_attr('from', str(value)) self._set_attr('from', str(value))
def get_stamp(self): def get_stamp(self):
timestamp = self._get_attr('stamp') timestamp = self._get_attr('stamp')
return xep_0082.parse('%sZ' % timestamp) if timestamp else None return xep_0082.parse('%sZ' % timestamp)
def set_stamp(self, value): def set_stamp(self, value):
if isinstance(value, dt.datetime): if isinstance(value, dt.datetime):

View File

@@ -70,7 +70,7 @@ class XEP_0092(BasePlugin):
iq['software_version']['os'] = self.os iq['software_version']['os'] = self.os
iq.send() iq.send()
def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None): def get_version(self, jid, ifrom=None):
""" """
Retrieve the software version of a remote agent. Retrieve the software version of a remote agent.
@@ -82,4 +82,14 @@ class XEP_0092(BasePlugin):
iq['from'] = ifrom iq['from'] = ifrom
iq['type'] = 'get' iq['type'] = 'get'
iq['query'] = Version.namespace iq['query'] = Version.namespace
return iq.send(block=block, timeout=timeout, callback=callback)
result = iq.send()
if result and result['type'] != 'error':
values = result['software_version'].values
del values['lang']
return values
return False
XEP_0092.getVersion = XEP_0092.get_version

View File

@@ -1,16 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0095 import stanza
from sleekxmpp.plugins.xep_0095.stanza import SI
from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095
register_plugin(XEP_0095)

View File

@@ -1,25 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class SI(ElementBase):
name = 'si'
namespace = 'http://jabber.org/protocol/si'
plugin_attrib = 'si'
interfaces = set(['id', 'mime_type', 'profile'])
def get_mime_type(self):
return self._get_attr('mime-type', 'application/octet-stream')
def set_mime_type(self, value):
self._set_attr('mime-type', value)
def del_mime_type(self):
self._del_attr('mime-type')

View File

@@ -1,214 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import threading
from uuid import uuid4
from sleekxmpp import Iq, Message
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0095 import stanza, SI
log = logging.getLogger(__name__)
SOCKS5 = 'http://jabber.org/protocol/bytestreams'
IBB = 'http://jabber.org/protocol/ibb'
class XEP_0095(BasePlugin):
name = 'xep_0095'
description = 'XEP-0095: Stream Initiation'
dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065'])
stanza = stanza
def plugin_init(self):
self._profiles = {}
self._methods = {}
self._methods_order = []
self._pending_lock = threading.Lock()
self._pending= {}
self.register_method(SOCKS5, 'xep_0065', 100)
self.register_method(IBB, 'xep_0047', 50)
register_stanza_plugin(Iq, SI)
register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation)
self.xmpp.register_handler(
Callback('SI Request',
StanzaPath('iq@type=set/si'),
self._handle_request))
self.api.register(self._add_pending, 'add_pending', default=True)
self.api.register(self._get_pending, 'get_pending', default=True)
self.api.register(self._del_pending, 'del_pending', default=True)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(SI.namespace)
def plugin_end(self):
self.xmpp.remove_handler('SI Request')
self.xmpp['xep_0030'].del_feature(feature=SI.namespace)
def register_profile(self, profile_name, plugin):
self._profiles[profile_name] = plugin
def unregister_profile(self, profile_name):
try:
del self._profiles[profile_name]
except KeyError:
pass
def register_method(self, method, plugin_name, order=50):
self._methods[method] = (plugin_name, order)
self._methods_order.append((order, method, plugin_name))
self._methods_order.sort()
def unregister_method(self, method):
if method in self._methods:
plugin_name, order = self._methods[method]
del self._methods[method]
self._methods_order.remove((order, method, plugin_name))
self._methods_order.sort()
def _handle_request(self, iq):
profile = iq['si']['profile']
sid = iq['si']['id']
if not sid:
raise XMPPError(etype='modify', condition='bad-request')
if profile not in self._profiles:
raise XMPPError(
etype='modify',
condition='bad-request',
extension='bad-profile',
extension_ns=SI.namespace)
neg = iq['si']['feature_neg']['form']['fields']
options = neg['stream-method']['options'] or []
methods = []
for opt in options:
methods.append(opt['value'])
for method in methods:
if method in self._methods:
supported = True
break
else:
raise XMPPError('bad-request',
extension='no-valid-streams',
extension_ns=SI.namespace)
selected_method = None
log.debug('Available: %s', methods)
for order, method, plugin in self._methods_order:
log.debug('Testing: %s', method)
if method in methods:
selected_method = method
break
receiver = iq['to']
sender = iq['from']
self.api['add_pending'](receiver, sid, sender, {
'response_id': iq['id'],
'method': selected_method,
'profile': profile
})
self.xmpp.event('si_request', iq)
def offer(self, jid, sid=None, mime_type=None, profile=None,
methods=None, payload=None, ifrom=None,
**iqargs):
if sid is None:
sid = uuid4().hex
if methods is None:
methods = list(self._methods.keys())
if not isinstance(methods, (list, tuple, set)):
methods = [methods]
si = self.xmpp.Iq()
si['to'] = jid
si['from'] = ifrom
si['type'] = 'set'
si['si']['id'] = sid
si['si']['mime_type'] = mime_type
si['si']['profile'] = profile
if not isinstance(payload, (list, tuple, set)):
payload = [payload]
for item in payload:
si['si'].append(item)
si['si']['feature_neg']['form'].add_field(
var='stream-method',
ftype='list-single',
options=methods)
return si.send(**iqargs)
def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None):
stream = self.api['get_pending'](ifrom, sid, jid)
iq = self.xmpp.Iq()
iq['id'] = stream['response_id']
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'result'
if payload:
iq['si'].append(payload)
iq['si']['feature_neg']['form']['type'] = 'submit'
iq['si']['feature_neg']['form'].add_field(
var='stream-method',
ftype='list-single',
value=stream['method'])
if ifrom is None:
ifrom = self.xmpp.boundjid
method_plugin = self._methods[stream['method']][0]
self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid)
self.api['del_pending'](ifrom, sid, jid)
if stream_handler:
self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
stream_handler,
threaded=True,
disposable=True)
return iq.send()
def decline(self, jid, sid, ifrom=None):
stream = self.api['get_pending'](ifrom, sid, jid)
if not stream:
return
iq = self.xmpp.Iq()
iq['id'] = stream['response_id']
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'error'
iq['error']['condition'] = 'forbidden'
iq['error']['text'] = 'Offer declined'
self.api['del_pending'](ifrom, sid, jid)
return iq.send()
def _add_pending(self, jid, node, ifrom, data):
with self._pending_lock:
self._pending[(jid, node, ifrom)] = data
def _get_pending(self, jid, node, ifrom, data):
with self._pending_lock:
return self._pending.get((jid, node, ifrom), None)
def _del_pending(self, jid, node, ifrom, data):
with self._pending_lock:
if (jid, node, ifrom) in self._pending:
del self._pending[(jid, node, ifrom)]

View File

@@ -1,16 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0096 import stanza
from sleekxmpp.plugins.xep_0096.stanza import File
from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096
register_plugin(XEP_0096)

View File

@@ -1,58 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq, Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0096 import stanza, File
log = logging.getLogger(__name__)
class XEP_0096(BasePlugin):
name = 'xep_0096'
description = 'XEP-0096: SI File Transfer'
dependencies = set(['xep_0095'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File)
self.xmpp['xep_0095'].register_profile(File.namespace, self)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(File.namespace)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=File.namespace)
self.xmpp['xep_0095'].unregister_profile(File.namespace, self)
def request_file_transfer(self, jid, sid=None, name=None, size=None,
desc=None, hash=None, date=None,
allow_ranged=False, mime_type=None,
**iqargs):
data = File()
data['name'] = name
data['size'] = size
data['date'] = date
data['desc'] = desc
if allow_ranged:
data.enable('range')
return self.xmpp['xep_0095'].offer(jid,
sid=sid,
mime_type=mime_type,
profile=File.namespace,
payload=data,
**iqargs)

View File

@@ -1,48 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import datetime as dt
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
from sleekxmpp.plugins import xep_0082
class File(ElementBase):
name = 'file'
namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
plugin_attrib = 'file'
interfaces = set(['name', 'size', 'date', 'hash', 'desc'])
sub_interfaces = set(['desc'])
def set_size(self, value):
self._set_attr('size', str(value))
def get_date(self):
timestamp = self._get_attr('date')
return xep_0082.parse(timestamp)
def set_date(self, value):
if isinstance(value, dt.datetime):
value = xep_0082.format_datetime(value)
self._set_attr('date', value)
class Range(ElementBase):
name = 'range'
namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
plugin_attrib = 'range'
interfaces = set(['length', 'offset'])
def set_length(self, value):
self._set_attr('length', str(value))
def set_offset(self, value):
self._set_attr('offset', str(value))
register_stanza_plugin(File, Range)

View File

@@ -9,9 +9,8 @@
import logging import logging
import hashlib import hashlib
import base64 import base64
import threading
from sleekxmpp import __version__ import sleekxmpp
from sleekxmpp.stanza import StreamFeatures, Presence, Iq from sleekxmpp.stanza import StreamFeatures, Presence, Iq
from sleekxmpp.xmlstream import register_stanza_plugin, JID from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.handler import Callback
@@ -46,7 +45,8 @@ class XEP_0115(BasePlugin):
'md5': hashlib.md5} 'md5': hashlib.md5}
if self.caps_node is None: if self.caps_node is None:
self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__ ver = sleekxmpp.__version__
self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver
register_stanza_plugin(Presence, stanza.Capabilities) register_stanza_plugin(Presence, stanza.Capabilities)
register_stanza_plugin(StreamFeatures, stanza.Capabilities) register_stanza_plugin(StreamFeatures, stanza.Capabilities)
@@ -90,9 +90,6 @@ class XEP_0115(BasePlugin):
disco.assign_verstring = self.assign_verstring disco.assign_verstring = self.assign_verstring
disco.get_verstring = self.get_verstring disco.get_verstring = self.get_verstring
self._processing_lock = threading.Lock()
self._processing = set()
def plugin_end(self): def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
self.xmpp.del_filter('out', self._filter_add_caps) self.xmpp.del_filter('out', self._filter_add_caps)
@@ -138,22 +135,17 @@ class XEP_0115(BasePlugin):
def _process_caps(self, pres): def _process_caps(self, pres):
if not pres['caps']['hash']: if not pres['caps']['hash']:
log.debug("Received unsupported legacy caps: %s, %s, %s", log.debug("Received unsupported legacy caps.")
pres['caps']['node'],
pres['caps']['ver'],
pres['caps']['ext'])
self.xmpp.event('entity_caps_legacy', pres) self.xmpp.event('entity_caps_legacy', pres)
return return
ver = pres['caps']['ver']
existing_verstring = self.get_verstring(pres['from'].full) existing_verstring = self.get_verstring(pres['from'].full)
if str(existing_verstring) == str(ver): if str(existing_verstring) == str(pres['caps']['ver']):
return return
existing_caps = self.get_caps(verstring=ver) existing_caps = self.get_caps(verstring=pres['caps']['ver'])
if existing_caps is not None: if existing_caps is not None:
self.assign_verstring(pres['from'], ver) self.assign_verstring(pres['from'], pres['caps']['ver'])
return return
if pres['caps']['hash'] not in self.hashes: if pres['caps']['hash'] not in self.hashes:
@@ -164,16 +156,9 @@ class XEP_0115(BasePlugin):
except XMPPError: except XMPPError:
return return
# Only lookup the same caps once at a time. log.debug("New caps verification string: %s", pres['caps']['ver'])
with self._processing_lock:
if ver in self._processing:
log.debug('Already processing verstring %s' % ver)
return
self._processing.add(ver)
log.debug("New caps verification string: %s", ver)
try: try:
node = '%s#%s' % (pres['caps']['node'], ver) node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
caps = self.xmpp['xep_0030'].get_info(pres['from'], node) caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
if isinstance(caps, Iq): if isinstance(caps, Iq):
@@ -183,10 +168,7 @@ class XEP_0115(BasePlugin):
pres['caps']['ver']): pres['caps']['ver']):
self.assign_verstring(pres['from'], pres['caps']['ver']) self.assign_verstring(pres['from'], pres['caps']['ver'])
except XMPPError: except XMPPError:
log.debug("Could not retrieve disco#info results for caps for %s", node) log.debug("Could not retrieve disco#info results for caps")
with self._processing_lock:
self._processing.remove(ver)
def _validate_caps(self, caps, hash, check_verstring): def _validate_caps(self, caps, hash, check_verstring):
# Check Identities # Check Identities
@@ -197,6 +179,7 @@ class XEP_0115(BasePlugin):
return False return False
# Check Features # Check Features
full_features = caps.get_features(dedupe=False) full_features = caps.get_features(dedupe=False)
deduped_features = caps.get_features() deduped_features = caps.get_features()
if len(full_features) != len(deduped_features): if len(full_features) != len(deduped_features):
@@ -207,32 +190,29 @@ class XEP_0115(BasePlugin):
form_types = [] form_types = []
deduped_form_types = set() deduped_form_types = set()
for stanza in caps['substanzas']: for stanza in caps['substanzas']:
if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
log.debug("Non form extension found, ignoring for caps") if 'FORM_TYPE' in stanza['fields']:
caps.xml.remove(stanza.xml) f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
continue form_types.append(f_type)
if 'FORM_TYPE' in stanza['fields']: deduped_form_types.add(f_type)
f_type = tuple(stanza['fields']['FORM_TYPE']['value']) if len(form_types) != len(deduped_form_types):
form_types.append(f_type) log.debug("Duplicated FORM_TYPE values, " + \
deduped_form_types.add(f_type) "invalid for caps")
if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
return False
if len(f_type) > 1:
deduped_type = set(f_type)
if len(f_type) != len(deduped_type):
log.debug("Extra FORM_TYPE data, invalid for caps")
return False return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden': if len(f_type) > 1:
log.debug("Field FORM_TYPE type not 'hidden', " + \ deduped_type = set(f_type)
"ignoring form for caps") if len(f_type) != len(deduped_type):
log.debug("Extra FORM_TYPE data, invalid for caps")
return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")
caps.xml.remove(stanza.xml) caps.xml.remove(stanza.xml)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")
caps.xml.remove(stanza.xml)
verstring = self.generate_verstring(caps, hash) verstring = self.generate_verstring(caps, hash)
if verstring != check_verstring: if verstring != check_verstring:
@@ -292,7 +272,7 @@ class XEP_0115(BasePlugin):
binary = hash(S.encode('utf8')).digest() binary = hash(S.encode('utf8')).digest()
return base64.b64encode(binary).decode('utf-8') return base64.b64encode(binary).decode('utf-8')
def update_caps(self, jid=None, node=None, preserve=False): def update_caps(self, jid=None, node=None):
try: try:
info = self.xmpp['xep_0030'].get_info(jid, node, local=True) info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
if isinstance(info, Iq): if isinstance(info, Iq):
@@ -306,11 +286,19 @@ class XEP_0115(BasePlugin):
self.assign_verstring(jid, ver) self.assign_verstring(jid, ver)
if self.xmpp.session_started_event.is_set() and self.broadcast: if self.xmpp.session_started_event.is_set() and self.broadcast:
if self.xmpp.is_component or preserve: # Check if we've sent directed presence. If we haven't, we
# can just send a normal presence stanza. If we have, then
# we will send presence to each contact individually so
# that we don't clobber existing statuses.
directed = False
for contact in self.xmpp.roster[jid]:
if self.xmpp.roster[jid][contact].last_status is not None:
directed = True
if not directed:
self.xmpp.roster[jid].send_last_presence()
else:
for contact in self.xmpp.roster[jid]: for contact in self.xmpp.roster[jid]:
self.xmpp.roster[jid][contact].send_last_presence() self.xmpp.roster[jid][contact].send_last_presence()
else:
self.xmpp.roster[jid].send_last_presence()
except XMPPError: except XMPPError:
return return

View File

@@ -1,16 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0152 import stanza
from sleekxmpp.plugins.xep_0152.stanza import Reachability
from sleekxmpp.plugins.xep_0152.reachability import XEP_0152
register_plugin(XEP_0152)

View File

@@ -1,93 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.plugins.base import BasePlugin
from sleekxmpp.plugins.xep_0152 import stanza, Reachability
log = logging.getLogger(__name__)
class XEP_0152(BasePlugin):
"""
XEP-0152: Reachability Addresses
"""
name = 'xep_0152'
description = 'XEP-0152: Reachability Addresses'
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace)
self.xmpp['xep_0163'].remove_interest(Reachability.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
def publish_reachability(self, addresses, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish alternative addresses where the user can be reached.
Arguments:
addresses -- A list of dictionaries containing the URI and
optional description for each address.
options -- Optional form of publish options.
ifrom -- Specify the sender's JID.
block -- Specify if the send call will block until a response
is received, or a timeout occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
if not isinstance(addresses, (list, tuple)):
addresses = [addresses]
reach = Reachability()
for address in addresses:
if not hasattr(address, 'items'):
address = {'uri': address}
addr = stanza.Address()
for key, val in address.items():
addr[key] = val
reach.append(addr)
return self.xmpp['xep_0163'].publish(reach,
node=Reachability.namespace,
options=options,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
"""
Clear existing user activity information to stop notifications.
Arguments:
ifrom -- Specify the sender's JID.
block -- Specify if the send call will block until a response
is received, or a timeout occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
reach = Reachability()
return self.xmpp['xep_0163'].publish(reach,
node=Reachability.namespace,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)

View File

@@ -1,29 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class Reachability(ElementBase):
name = 'reach'
namespace = 'urn:xmpp:reach:0'
plugin_attrib = 'reach'
interfaces = set()
class Address(ElementBase):
name = 'addr'
namespace = 'urn:xmpp:reach:0'
plugin_attrib = 'address'
plugin_multi_attrib = 'addresses'
interfaces = set(['uri', 'desc'])
lang_interfaces = set(['desc'])
sub_interfaces = set(['desc'])
register_stanza_plugin(Reachability, Address, iterable=True)

View File

@@ -10,9 +10,12 @@ import hashlib
import logging import logging
import threading import threading
from sleekxmpp import JID
from sleekxmpp.stanza import Presence from sleekxmpp.stanza import Presence
from sleekxmpp.exceptions import XMPPError from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.plugins.base import BasePlugin from sleekxmpp.plugins.base import BasePlugin
from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate
@@ -75,17 +78,8 @@ class XEP_0153(BasePlugin):
self.xmpp.roster[jid].send_last_presence() self.xmpp.roster[jid].send_last_presence()
def _start(self, event): def _start(self, event):
try: vcard = self.xmpp['xep_0054'].get_vcard()
vcard = self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare) self._allow_advertising.set()
data = vcard['vcard_temp']['PHOTO']['BINVAL']
if not data:
new_hash = ''
else:
new_hash = hashlib.sha1(data).hexdigest()
self.api['set_hash'](self.xmpp.boundjid, args=new_hash)
self._allow_advertising.set()
except XMPPError:
log.debug('Could not retrieve vCard for %s' % self.xmpp.boundjid.bare)
def _end(self, event): def _end(self, event):
self._allow_advertising.clear() self._allow_advertising.clear()
@@ -124,13 +118,6 @@ class XEP_0153(BasePlugin):
log.debug('Could not retrieve vCard for %s' % jid) log.debug('Could not retrieve vCard for %s' % jid)
def _recv_presence(self, pres): def _recv_presence(self, pres):
try:
if pres['muc']['affiliation']:
# Don't process vCard avatars for MUC occupants
# since they all share the same bare JID.
return
except: pass
if not pres.match('presence/vcard_temp_update'): if not pres.match('presence/vcard_temp_update'):
self.api['set_hash'](pres['from'], args=None) self.api['set_hash'](pres['from'], args=None)
return return
@@ -138,7 +125,7 @@ class XEP_0153(BasePlugin):
data = pres['vcard_temp_update']['photo'] data = pres['vcard_temp_update']['photo']
if data is None: if data is None:
return return
elif data == '' or data != self.api['get_hash'](pres['from']): elif data == '' or data != self.api['get_hash'](pres['to']):
ifrom = pres['to'] if self.xmpp.is_component else None ifrom = pres['to'] if self.xmpp.is_component else None
self.api['reset_hash'](pres['from'], ifrom=ifrom) self.api['reset_hash'](pres['from'], ifrom=ifrom)
self.xmpp.event('vcard_avatar_update', pres) self.xmpp.event('vcard_avatar_update', pres)

Some files were not shown because too many files have changed in this diff Show More