Compare commits
151 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b549db959a | ||
![]() |
d5188ac68a | ||
![]() |
ada9444bf8 | ||
![]() |
acc52fd935 | ||
![]() |
1100ff1feb | ||
![]() |
c17fc3a869 | ||
![]() |
4dba697075 | ||
![]() |
e42d651d7e | ||
![]() |
4305eddb4f | ||
![]() |
c2dc44cfd1 | ||
![]() |
5fc14de32e | ||
![]() |
d245558fd5 | ||
![]() |
9d45370e8a | ||
![]() |
cc1cc61d36 | ||
![]() |
c6740a4908 | ||
![]() |
55114bcffe | ||
![]() |
4fa5dedc47 | ||
![]() |
5525ef2285 | ||
![]() |
a7ac969215 | ||
![]() |
329cb5a9f8 | ||
![]() |
d9b47b33f5 | ||
![]() |
3582ac9941 | ||
![]() |
2a127a57a7 | ||
![]() |
7059400020 | ||
![]() |
0b14ef82d4 | ||
![]() |
83953af53d | ||
![]() |
110cf25c6d | ||
![]() |
f2bf6072ec | ||
![]() |
5f9abe2e0e | ||
![]() |
ea65b672e7 | ||
![]() |
93c705fb31 | ||
![]() |
0724f623bb | ||
![]() |
82e549c0e9 | ||
![]() |
1aa15792b4 | ||
![]() |
ffb2b6bc04 | ||
![]() |
27f98bf22c | ||
![]() |
3978078710 | ||
![]() |
00a0698720 | ||
![]() |
4a24f58be2 | ||
![]() |
da14ce16ec | ||
![]() |
18e5abb9dd | ||
![]() |
1a75b76916 | ||
![]() |
53b56899a0 | ||
![]() |
804b23d390 | ||
![]() |
04eaf52b1d | ||
![]() |
dc7fef1064 | ||
![]() |
488c433555 | ||
![]() |
9c5dd024b1 | ||
![]() |
6e61adf3db | ||
![]() |
041bd63864 | ||
![]() |
a366482551 | ||
![]() |
a721084f6e | ||
![]() |
1b4187fa56 | ||
![]() |
cf7a60705e | ||
![]() |
349b05b9b7 | ||
![]() |
9fbacf377a | ||
![]() |
2da9e35cbc | ||
![]() |
abcec1e2d3 | ||
![]() |
eeab646bfa | ||
![]() |
2c69144189 | ||
![]() |
f54ebec654 | ||
![]() |
2042e1a4d5 | ||
![]() |
be14f0cc52 | ||
![]() |
edd9199be8 | ||
![]() |
bb094cc649 | ||
![]() |
dbaa6ed952 | ||
![]() |
8c94d894ab | ||
![]() |
ffc7eac4dc | ||
![]() |
555fd6d926 | ||
![]() |
c024ac8f0b | ||
![]() |
f00177c0cf | ||
![]() |
224d7ae133 | ||
![]() |
9b25a7cf77 | ||
![]() |
7a908ac07b | ||
![]() |
92901637ec | ||
![]() |
3590b663ed | ||
![]() |
a33bde9cc3 | ||
![]() |
ac50fdccfc | ||
![]() |
a0c6bf15e9 | ||
![]() |
a8ac115310 | ||
![]() |
1345b7c1d0 | ||
![]() |
d60a652259 | ||
![]() |
61a7cecb31 | ||
![]() |
192b7e0349 | ||
![]() |
80b60fc048 | ||
![]() |
842157a6cc | ||
![]() |
a63cc01482 | ||
![]() |
1bbb6f3ff9 | ||
![]() |
93894247a4 | ||
![]() |
16bb5e2537 | ||
![]() |
d19a6e05b2 | ||
![]() |
86e85f9835 | ||
![]() |
cc145d20b0 | ||
![]() |
881d9040c4 | ||
![]() |
1e77ea0944 | ||
![]() |
140f0885b2 | ||
![]() |
83f71a6610 | ||
![]() |
271343a32d | ||
![]() |
48857b0030 | ||
![]() |
1fe7f5f4e6 | ||
![]() |
81b7b2c190 | ||
![]() |
460de7d301 | ||
![]() |
69022c6db7 | ||
![]() |
9044807121 | ||
![]() |
24264d3a07 | ||
![]() |
8bc70264ef | ||
![]() |
c16b862200 | ||
![]() |
a96f608469 | ||
![]() |
e1f25604ec | ||
![]() |
0fe057b5c3 | ||
![]() |
be76dda21d | ||
![]() |
ecd124dd06 | ||
![]() |
4a8951c4ee | ||
![]() |
8afba7de85 | ||
![]() |
1ce42d3a2f | ||
![]() |
2f4d811db4 | ||
![]() |
61127f521d | ||
![]() |
063e73c0d2 | ||
![]() |
d261318e1a | ||
![]() |
d33cc00fe9 | ||
![]() |
27582f6fd2 | ||
![]() |
e328ff4833 | ||
![]() |
403462fdb8 | ||
![]() |
f22d8e67b4 | ||
![]() |
35f33f1614 | ||
![]() |
c9f8ddff65 | ||
![]() |
f5ae98aaf1 | ||
![]() |
073e85381a | ||
![]() |
afc939708f | ||
![]() |
aabec8b993 | ||
![]() |
e5e2fbb16b | ||
![]() |
3dd379cdf1 | ||
![]() |
a20582aba4 | ||
![]() |
09cdbf1b76 | ||
![]() |
ca306e7cec | ||
![]() |
1bf34f7fe6 | ||
![]() |
4144d60017 | ||
![]() |
7265682a4d | ||
![]() |
08c62a6bf1 | ||
![]() |
d61f1cd035 | ||
![]() |
1063feb33b | ||
![]() |
79f3c1ac8f | ||
![]() |
a5c03b763a | ||
![]() |
3670d82f1c | ||
![]() |
e94a73553d | ||
![]() |
577fd71472 | ||
![]() |
ef1c4368d0 | ||
![]() |
48def71d0c | ||
![]() |
c8c20fff71 | ||
![]() |
75a18b5ffe | ||
![]() |
ea3d39b50e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ slixmpp.egg-info/
|
||||
*~
|
||||
.baboon/
|
||||
.DS_STORE
|
||||
.idea/
|
||||
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
install:
|
||||
- "pip install ."
|
||||
script: testall.py
|
@@ -1,6 +1,7 @@
|
||||
include README.rst
|
||||
include LICENSE
|
||||
include testall.py
|
||||
include run_tests.py
|
||||
include slixmpp/stringprep.pyx
|
||||
recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
|
||||
recursive-include examples *.py
|
||||
recursive-include tests *.py
|
||||
|
@@ -8,6 +8,14 @@ Slixmpp's goals is to only rewrite the core of the library (the low level
|
||||
socket handling, the timers, the events dispatching) in order to remove all
|
||||
threads.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Slixmpp can make use of cython to improve performance on critical modules.
|
||||
To do that, cython3 is necessary along with libidn headers. Otherwise,
|
||||
no compilation is needed. Building is done by running setup.py::
|
||||
|
||||
python3 setup.py build_ext --inplace
|
||||
|
||||
Documentation and Testing
|
||||
-------------------------
|
||||
|
@@ -48,9 +48,9 @@ copyright = u'2011, Nathan Fritz, Lance Stout'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
version = '1.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
release = '1.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item.
|
||||
In this case, the owning JID and node are provided with the
|
||||
parameters ``ijid`` and ``node``.
|
||||
|
||||
Peforming Disco Queries
|
||||
-----------------------
|
||||
Performing Disco Queries
|
||||
------------------------
|
||||
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
|
||||
and their nodes for disco information. Since these methods are wrappers for
|
||||
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
|
||||
@@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
info = self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
ifrom='baz@mycomponent.example.com',
|
||||
block=True,
|
||||
timeout=30)
|
||||
info = yield from self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
ifrom='baz@mycomponent.example.com',
|
||||
timeout=30)
|
||||
|
||||
items = self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
|
@@ -160,9 +160,9 @@ if __name__ == '__main__':
|
||||
|
||||
myDevice = TheDevice(args.nodeid);
|
||||
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
|
||||
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"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10);
|
||||
xmpp.beClientOrServer(server=True)
|
||||
|
@@ -76,10 +76,6 @@ class Disco(slixmpp.ClientXMPP):
|
||||
|
||||
try:
|
||||
if self.get in self.info_types:
|
||||
# By using block=True, the result stanza will be
|
||||
# returned. Execution will block until the reply is
|
||||
# received. Non-blocking options would be to listen
|
||||
# for the disco_info event, or passing a handler
|
||||
# function using the callback parameter.
|
||||
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node)
|
||||
@@ -162,4 +158,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
97
examples/http_over_xmpp.py
Normal file
97
examples/http_over_xmpp.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp import ClientXMPP
|
||||
|
||||
from optparse import OptionParser
|
||||
import logging
|
||||
import getpass
|
||||
|
||||
|
||||
class HTTPOverXMPPClient(ClientXMPP):
|
||||
def __init__(self, jid, password):
|
||||
ClientXMPP.__init__(self, jid, password)
|
||||
self.register_plugin('xep_0332') # HTTP over XMPP Transport
|
||||
self.add_event_handler(
|
||||
'session_start', self.session_start, threaded=True
|
||||
)
|
||||
self.add_event_handler('http_request', self.http_request_received)
|
||||
self.add_event_handler('http_response', self.http_response_received)
|
||||
|
||||
def http_request_received(self, iq):
|
||||
pass
|
||||
|
||||
def http_response_received(self, iq):
|
||||
print('HTTP Response Received : %s' % iq)
|
||||
print('From : %s' % iq['from'])
|
||||
print('To : %s' % iq['to'])
|
||||
print('Type : %s' % iq['type'])
|
||||
print('Headers : %s' % iq['resp']['headers'])
|
||||
print('Code : %s' % iq['resp']['code'])
|
||||
print('Message : %s' % iq['resp']['message'])
|
||||
print('Data : %s' % iq['resp']['data'])
|
||||
|
||||
def session_start(self, event):
|
||||
# TODO: Fill in the blanks
|
||||
self['xep_0332'].send_request(
|
||||
to='?', method='?', resource='?', headers={}
|
||||
)
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
#
|
||||
# NOTE: To run this example, fill up the blanks in session_start() and
|
||||
# use the following command.
|
||||
#
|
||||
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
|
||||
#
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
# Output verbosity options.
|
||||
parser.add_option(
|
||||
'-v', '--verbose', help='set logging to DEBUG', action='store_const',
|
||||
dest='loglevel', const=logging.DEBUG, default=logging.ERROR
|
||||
)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_option('-J', '--jid', dest='jid', help='JID')
|
||||
parser.add_option('-P', '--password', dest='password', help='Password')
|
||||
|
||||
# XMPP server ip and port options.
|
||||
parser.add_option(
|
||||
'-i', '--ipaddr', dest='ipaddr',
|
||||
help='IP Address of the XMPP server', default=None
|
||||
)
|
||||
parser.add_option(
|
||||
'-p', '--port', dest='port',
|
||||
help='Port of the XMPP server', default=None
|
||||
)
|
||||
|
||||
opts, args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=opts.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if opts.jid is None:
|
||||
opts.jid = input('Username: ')
|
||||
if opts.password is None:
|
||||
opts.password = getpass.getpass('Password: ')
|
||||
|
||||
xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
|
@@ -100,7 +100,7 @@ def on_session2(event):
|
||||
new_xmpp.update_roster(jid,
|
||||
name = item['name'],
|
||||
groups = item['groups'])
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.add_event_handler('session_start', on_session2)
|
||||
|
||||
new_xmpp.connect()
|
||||
|
@@ -5,20 +5,16 @@ import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import asyncio
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp.xmlstream import ET, tostring
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
|
||||
def make_callback():
|
||||
future = asyncio.Future()
|
||||
def callback(result):
|
||||
future.set_result(result)
|
||||
return future, callback
|
||||
|
||||
class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password, server,
|
||||
node=None, action='list', data=''):
|
||||
node=None, action='nodes', data=''):
|
||||
super(PubsubClient, self).__init__(jid, password)
|
||||
|
||||
self.register_plugin('xep_0030')
|
||||
@@ -36,87 +32,83 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
try:
|
||||
getattr(self, self.action)()
|
||||
yield from getattr(self, self.action)()
|
||||
except:
|
||||
logging.error('Could not execute: %s' % self.action)
|
||||
logging.error('Could not execute: %s', self.action)
|
||||
self.disconnect()
|
||||
|
||||
def nodes(self):
|
||||
future, callback = make_callback()
|
||||
try:
|
||||
self['xep_0060'].get_nodes(self.pubsub_server, self.node, callback=callback)
|
||||
result = yield from future
|
||||
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||
for item in result['disco_items']['items']:
|
||||
print(' - %s' % str(item))
|
||||
except:
|
||||
logging.error('Could not retrieve node list.')
|
||||
logging.info(' - %s', str(item))
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve node list: %s', error.format())
|
||||
|
||||
def create(self):
|
||||
try:
|
||||
self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||
except:
|
||||
logging.error('Could not create node: %s' % self.node)
|
||||
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||
logging.info('Created node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not create node %s: %s', self.node, error.format())
|
||||
|
||||
def delete(self):
|
||||
try:
|
||||
self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||
print('Deleted node: %s' % self.node)
|
||||
except:
|
||||
logging.error('Could not delete node: %s' % self.node)
|
||||
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||
logging.info('Deleted node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not delete node %s: %s', self.node, error.format())
|
||||
|
||||
def publish(self):
|
||||
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||
future, callback = make_callback()
|
||||
try:
|
||||
self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload, callback=callback)
|
||||
result = yield from future
|
||||
id = result['pubsub']['publish']['item']['id']
|
||||
print('Published at item id: %s' % id)
|
||||
except:
|
||||
logging.error('Could not publish to: %s' % self.node)
|
||||
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not publish to %s: %s', self.node, error.format())
|
||||
|
||||
def get(self):
|
||||
future, callback = make_callback()
|
||||
try:
|
||||
self['xep_0060'].get_item(self.pubsub_server, self.node, self.data, callback=callback)
|
||||
result = yield from future
|
||||
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||
for item in result['pubsub']['items']['substanzas']:
|
||||
print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
|
||||
except:
|
||||
logging.error('Could not retrieve item %s from node %s' % (self.data, self.node))
|
||||
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
|
||||
|
||||
def retract(self):
|
||||
try:
|
||||
self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
print('Retracted item %s from node %s' % (self.data, self.node))
|
||||
except:
|
||||
logging.error('Could not retract item %s from node %s' % (self.data, self.node))
|
||||
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
logging.info('Retracted item %s from node %s', self.data, self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
|
||||
|
||||
def purge(self):
|
||||
try:
|
||||
self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
print('Purged all items from node %s' % self.node)
|
||||
except:
|
||||
logging.error('Could not purge items from node %s' % self.node)
|
||||
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
logging.info('Purged all items from node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not purge items from node %s: %s', self.node, error.format())
|
||||
|
||||
def subscribe(self):
|
||||
try:
|
||||
self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
|
||||
except:
|
||||
logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
|
||||
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
subscription = iq['pubsub']['subscription']
|
||||
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||
|
||||
def unsubscribe(self):
|
||||
try:
|
||||
self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
|
||||
except:
|
||||
logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))
|
||||
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||
|
||||
|
||||
|
||||
@@ -133,12 +125,12 @@ if __name__ == '__main__':
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.ERROR)
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.ERROR)
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
@@ -147,7 +139,7 @@ if __name__ == '__main__':
|
||||
help="password to use")
|
||||
|
||||
parser.add_argument("server")
|
||||
parser.add_argument("action", choice=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
|
||||
parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
|
||||
parser.add_argument("node", nargs='?')
|
||||
parser.add_argument("data", nargs='?')
|
||||
|
||||
@@ -171,4 +163,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
@@ -59,7 +59,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
self.get_roster(callback=callback)
|
||||
yield from future
|
||||
except IqError as err:
|
||||
print('Error: %' % err.iq['error']['condition'])
|
||||
print('Error: %s' % err.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
print('Error: Request timed out')
|
||||
self.send_presence()
|
||||
|
90
examples/s5b_transfer/s5b_receiver.py
Executable file
90
examples/s5b_transfer/s5b_receiver.py
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
|
||||
|
||||
class S5BReceiver(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic example of creating and using a SOCKS5 bytestream.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, filename):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.file = open(filename, 'wb')
|
||||
|
||||
self.add_event_handler("socks5_connected", self.stream_opened)
|
||||
self.add_event_handler("socks5_data", self.stream_data)
|
||||
self.add_event_handler("socks5_closed", self.stream_closed)
|
||||
|
||||
def stream_opened(self, sid):
|
||||
logging.info('Stream opened. %s', sid)
|
||||
|
||||
def stream_data(self, data):
|
||||
self.file.write(data)
|
||||
|
||||
def stream_closed(self, exception):
|
||||
logging.info('Stream closed. %s', exception)
|
||||
self.file.close()
|
||||
self.disconnect()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
|
||||
# Output verbosity options.
|
||||
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
parser.add_argument("-o", "--out", dest="filename",
|
||||
help="file to save to")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
if args.filename is None:
|
||||
args.filename = input("File path: ")
|
||||
|
||||
# Setup the S5BReceiver and register plugins. Note that while plugins may
|
||||
# have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = S5BReceiver(args.jid, args.password, args.filename)
|
||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||
xmpp.register_plugin('xep_0065', {
|
||||
'auto_accept': True
|
||||
}) # SOCKS5 Bytestreams
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process(forever=False)
|
124
examples/s5b_transfer/s5b_sender.py
Executable file
124
examples/s5b_transfer/s5b_sender.py
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
|
||||
class S5BSender(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic example of creating and using a SOCKS5 bytestream.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, receiver, filename):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.receiver = receiver
|
||||
|
||||
self.file = open(filename, 'rb')
|
||||
|
||||
# The session_start event will be triggered when
|
||||
# the bot establishes its connection with the server
|
||||
# and the XML streams are ready for use.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
Typical actions for the session_start event are
|
||||
requesting the roster and broadcasting an initial
|
||||
presence stanza.
|
||||
|
||||
Arguments:
|
||||
event -- An empty dictionary. The session_start
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Open the S5B stream in which to write to.
|
||||
proxy = yield from self['xep_0065'].handshake(self.receiver)
|
||||
|
||||
# Send the entire file.
|
||||
while True:
|
||||
data = self.file.read(1048576)
|
||||
if not data:
|
||||
break
|
||||
yield from proxy.write(data)
|
||||
|
||||
# And finally close the stream.
|
||||
proxy.transport.write_eof()
|
||||
except (IqError, IqTimeout):
|
||||
print('File transfer errored')
|
||||
else:
|
||||
print('File transfer finished')
|
||||
finally:
|
||||
self.file.close()
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
|
||||
# Output verbosity options.
|
||||
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
parser.add_argument("-r", "--receiver", dest="receiver",
|
||||
help="JID of the receiver")
|
||||
parser.add_argument("-f", "--file", dest="filename",
|
||||
help="file to send")
|
||||
parser.add_argument("-m", "--use-messages", action="store_true",
|
||||
help="use messages instead of iqs for file transfer")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
if args.receiver is None:
|
||||
args.receiver = input("Receiver: ")
|
||||
if args.filename is None:
|
||||
args.filename = input("File path: ")
|
||||
|
||||
# Setup the S5BSender and register plugins. Note that while plugins may
|
||||
# have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = S5BSender(args.jid, args.password, args.receiver, args.filename)
|
||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||
xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process(forever=False)
|
2
setup.py
2
setup.py
@@ -52,7 +52,7 @@ setup(
|
||||
platforms=['any'],
|
||||
packages=packages,
|
||||
ext_modules=ext_modules,
|
||||
requires=['aiodns', 'pyasn1', 'pyasn1_modules'],
|
||||
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
|
||||
classifiers=CLASSIFIERS,
|
||||
cmdclass={'test': TestCommand}
|
||||
)
|
||||
|
@@ -6,6 +6,9 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
if hasattr(asyncio, 'sslproto'): # no ssl proto: very old asyncio = no need for this
|
||||
asyncio.sslproto._is_sslproto_available=lambda: False
|
||||
import logging
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
|
@@ -22,7 +22,6 @@ from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.stanza import Message, Presence, Iq, StreamError
|
||||
from slixmpp.stanza.roster import Roster
|
||||
from slixmpp.stanza.nick import Nick
|
||||
from slixmpp.stanza.htmlim import HTMLIM
|
||||
|
||||
from slixmpp.xmlstream import XMLStream, JID
|
||||
from slixmpp.xmlstream import ET, register_stanza_plugin
|
||||
@@ -46,8 +45,8 @@ class BaseXMPP(XMLStream):
|
||||
is used during initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, jid='', default_ns='jabber:client'):
|
||||
XMLStream.__init__(self)
|
||||
def __init__(self, jid='', default_ns='jabber:client', **kwargs):
|
||||
XMLStream.__init__(self, **kwargs)
|
||||
|
||||
self.default_ns = default_ns
|
||||
self.stream_ns = 'http://etherx.jabber.org/streams'
|
||||
@@ -221,7 +220,7 @@ class BaseXMPP(XMLStream):
|
||||
self.plugin[name].post_init()
|
||||
self.plugin[name].post_inited = True
|
||||
|
||||
def register_plugin(self, plugin, pconfig={}, module=None):
|
||||
def register_plugin(self, plugin, pconfig=None, module=None):
|
||||
"""Register and configure a plugin for use in this stream.
|
||||
|
||||
:param plugin: The name of the plugin class. Plugin names must
|
||||
|
@@ -50,7 +50,6 @@ class ClientXMPP(BaseXMPP):
|
||||
|
||||
:param jid: The JID of the XMPP user account.
|
||||
:param password: The password for the XMPP user account.
|
||||
:param ssl: **Deprecated.**
|
||||
:param plugin_config: A dictionary of plugin configurations.
|
||||
:param plugin_whitelist: A list of approved plugins that
|
||||
will be loaded when calling
|
||||
@@ -58,9 +57,15 @@ class ClientXMPP(BaseXMPP):
|
||||
:param escape_quotes: **Deprecated.**
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
|
||||
escape_quotes=True, sasl_mech=None, lang='en'):
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||
def __init__(self, jid, password, plugin_config=None,
|
||||
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
|
||||
lang='en', **kwargs):
|
||||
if not plugin_whitelist:
|
||||
plugin_whitelist = []
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
|
||||
|
||||
self.escape_quotes = escape_quotes
|
||||
self.plugin_config = plugin_config
|
||||
|
@@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP):
|
||||
Defaults to ``False``.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, secret, host=None, port=None,
|
||||
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
|
||||
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
|
||||
|
||||
if not plugin_whitelist:
|
||||
plugin_whitelist = []
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
if use_jc_ns:
|
||||
default_ns = 'jabber:client'
|
||||
else:
|
||||
|
@@ -56,6 +56,18 @@ class XMPPError(Exception):
|
||||
self.extension_ns = extension_ns
|
||||
self.extension_args = extension_args
|
||||
|
||||
def format(self):
|
||||
"""
|
||||
Format the error in a simple user-readable string.
|
||||
"""
|
||||
text = [self.etype, self.condition]
|
||||
if self.text:
|
||||
text.append(self.text)
|
||||
if self.extension:
|
||||
text.append(self.extension)
|
||||
# TODO: handle self.extension_args
|
||||
return ': '.join(text)
|
||||
|
||||
|
||||
class IqTimeout(XMPPError):
|
||||
|
||||
|
@@ -190,14 +190,14 @@ class FeatureMechanisms(BasePlugin):
|
||||
except sasl.SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
else:
|
||||
resp.send()
|
||||
|
||||
@@ -210,13 +210,13 @@ class FeatureMechanisms(BasePlugin):
|
||||
resp['value'] = self.mech.process(stanza['value'])
|
||||
except sasl.SASLCancelled:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
else:
|
||||
if resp.get_value() == '':
|
||||
resp.del_value()
|
||||
|
@@ -47,6 +47,7 @@ __all__ = [
|
||||
'xep_0108', # User Activity
|
||||
'xep_0115', # Entity Capabilities
|
||||
'xep_0118', # User Tune
|
||||
'xep_0122', # Data Forms Validation
|
||||
'xep_0128', # Extended Service Discovery
|
||||
'xep_0131', # Standard Headers and Internet Metadata
|
||||
'xep_0133', # Service Administration
|
||||
@@ -83,4 +84,5 @@ __all__ = [
|
||||
'xep_0319', # Last User Interaction in Presence
|
||||
'xep_0323', # IoT Systems Sensor Data
|
||||
'xep_0325', # IoT Systems Control
|
||||
'xep_0332', # HTTP Over XMPP Transport
|
||||
]
|
||||
|
47
slixmpp/plugins/google/auth/stanza.py
Normal file
47
slixmpp/plugins/google/auth/stanza.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.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('')
|
||||
|
||||
def get_client_uses_full_bind_result(self):
|
||||
return self.parent()._get_attr(self.discovery_attr) == 'true'
|
||||
|
||||
def set_client_uses_full_bind_result(self, 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)
|
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from slixmpp.stanza import Iq
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import MatchXPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.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, timeout=None, callback=None):
|
||||
last_time = self._last_result_time
|
||||
last_tid = self._last_result_tid
|
||||
|
||||
callback = lambda iq: self._update_last_results(iq, callback)
|
||||
|
||||
return self.search(newer_time=last_time,
|
||||
newer_tid=last_tid,
|
||||
timeout=timeout,
|
||||
callback=callback)
|
||||
|
||||
def _update_last_results(self, iq, callback=None):
|
||||
self._last_result_time = iq['gmail_messages']['result_time']
|
||||
threads = iq['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,
|
||||
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(timeout=timeout, callback=callback)
|
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.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, value):
|
||||
self._set_attr('source', str(value))
|
||||
|
||||
|
||||
register_stanza_plugin(NoSaveQuery, Item)
|
63
slixmpp/plugins/google/settings/settings.py
Normal file
63
slixmpp/plugins/google/settings/settings.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.stanza import Iq
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.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, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq.enable('google_settings')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def update(self, settings, 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(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)
|
@@ -13,8 +13,9 @@ class FormField(ElementBase):
|
||||
namespace = 'jabber:x:data'
|
||||
name = 'field'
|
||||
plugin_attrib = 'field'
|
||||
plugin_multi_attrib = 'fields'
|
||||
interfaces = set(('answer', 'desc', 'required', 'value',
|
||||
'options', 'label', 'type', 'var'))
|
||||
'label', 'type', 'var'))
|
||||
sub_interfaces = set(('desc',))
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
@@ -165,6 +166,7 @@ class FieldOption(ElementBase):
|
||||
plugin_attrib = 'option'
|
||||
interfaces = set(('label', 'value'))
|
||||
sub_interfaces = set(('value',))
|
||||
plugin_multi_attrib = 'options'
|
||||
|
||||
|
||||
FormField.addOption = FormField.add_option
|
||||
|
@@ -10,6 +10,7 @@ import copy
|
||||
import logging
|
||||
|
||||
from collections import OrderedDict
|
||||
from slixmpp.thirdparty import OrderedSet
|
||||
|
||||
from slixmpp.xmlstream import ElementBase, ET
|
||||
from slixmpp.plugins.xep_0004.stanza import FormField
|
||||
@@ -22,8 +23,7 @@ class Form(ElementBase):
|
||||
namespace = 'jabber:x:data'
|
||||
name = 'x'
|
||||
plugin_attrib = 'form'
|
||||
interfaces = set(('fields', 'instructions', 'items',
|
||||
'reported', 'title', 'type', 'values'))
|
||||
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
|
||||
sub_interfaces = set(('title',))
|
||||
form_types = set(('cancel', 'form', 'result', 'submit'))
|
||||
|
||||
@@ -43,12 +43,12 @@ class Form(ElementBase):
|
||||
|
||||
@property
|
||||
def field(self):
|
||||
return self['fields']
|
||||
return self.get_fields()
|
||||
|
||||
def set_type(self, ftype):
|
||||
self._set_attr('type', ftype)
|
||||
if ftype == 'submit':
|
||||
fields = self['fields']
|
||||
fields = self.get_fields()
|
||||
for var in fields:
|
||||
field = fields[var]
|
||||
del field['type']
|
||||
@@ -74,7 +74,8 @@ class Form(ElementBase):
|
||||
field['desc'] = desc
|
||||
field['required'] = required
|
||||
if options is not None:
|
||||
field['options'] = options
|
||||
for option in options:
|
||||
field.add_option(**option)
|
||||
else:
|
||||
del field['type']
|
||||
self.append(field)
|
||||
@@ -151,7 +152,6 @@ class Form(ElementBase):
|
||||
return fields
|
||||
|
||||
def get_instructions(self):
|
||||
instructions = ''
|
||||
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
||||
return "\n".join([instXML.text for instXML in instsXML])
|
||||
|
||||
@@ -170,7 +170,7 @@ class Form(ElementBase):
|
||||
def get_reported(self):
|
||||
fields = OrderedDict()
|
||||
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
||||
FormField.namespace))
|
||||
FormField.namespace))
|
||||
for field in xml:
|
||||
field = FormField(xml=field)
|
||||
fields[field['var']] = field
|
||||
@@ -178,7 +178,7 @@ class Form(ElementBase):
|
||||
|
||||
def get_values(self):
|
||||
values = OrderedDict()
|
||||
fields = self['fields']
|
||||
fields = self.get_fields()
|
||||
for var in fields:
|
||||
values[var] = fields[var]['value']
|
||||
return values
|
||||
@@ -195,7 +195,14 @@ class Form(ElementBase):
|
||||
fields = fields.items()
|
||||
for var, field in fields:
|
||||
field['var'] = var
|
||||
self.add_field(**field)
|
||||
self.add_field(
|
||||
var=field.get('var'),
|
||||
label=field.get('label'),
|
||||
desc=field.get('desc'),
|
||||
required=field.get('required'),
|
||||
value=field.get('value'),
|
||||
options=field.get('options'),
|
||||
type=field.get('type'))
|
||||
|
||||
def set_instructions(self, instructions):
|
||||
del self['instructions']
|
||||
@@ -213,17 +220,33 @@ class Form(ElementBase):
|
||||
self.add_item(item)
|
||||
|
||||
def set_reported(self, reported):
|
||||
"""
|
||||
This either needs a dictionary of dictionaries or a dictionary of form fields.
|
||||
:param reported:
|
||||
:return:
|
||||
"""
|
||||
for var in reported:
|
||||
field = reported[var]
|
||||
field['var'] = var
|
||||
self.add_reported(var, **field)
|
||||
|
||||
if isinstance(field, dict):
|
||||
self.add_reported(**field)
|
||||
else:
|
||||
reported = self.xml.find('{%s}reported' % self.namespace)
|
||||
if reported is None:
|
||||
reported = ET.Element('{%s}reported' % self.namespace)
|
||||
self.xml.append(reported)
|
||||
|
||||
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||
reported.append(fieldXML)
|
||||
new_field = FormField(xml=fieldXML)
|
||||
new_field.values = field.values
|
||||
|
||||
def set_values(self, values):
|
||||
fields = self['fields']
|
||||
fields = self.get_fields()
|
||||
for field in values:
|
||||
if field not in fields:
|
||||
if field not in self.get_fields():
|
||||
fields[field] = self.add_field(var=field)
|
||||
fields[field]['value'] = values[field]
|
||||
self.get_fields()[field]['value'] = values[field]
|
||||
|
||||
def merge(self, other):
|
||||
new = copy.copy(self)
|
||||
|
@@ -6,7 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from binding import py2xml, xml2py, xml2fault, fault2xml
|
||||
from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml
|
||||
from threading import RLock
|
||||
import abc
|
||||
import inspect
|
||||
@@ -18,6 +18,38 @@ import traceback
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def _isstr(obj):
|
||||
return isinstance(obj, str)
|
||||
|
||||
|
||||
# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3.
|
||||
# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six):
|
||||
#
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
def _add_metaclass(metaclass):
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
||||
def _intercept(method, name, public):
|
||||
def _resolver(instance, *args, **kwargs):
|
||||
log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
|
||||
@@ -68,7 +100,7 @@ def remote(function_argument, public = True):
|
||||
if hasattr(function_argument, '__call__'):
|
||||
return _intercept(function_argument, None, public)
|
||||
else:
|
||||
if not isinstance(function_argument, basestring):
|
||||
if not _isstr(function_argument):
|
||||
if not isinstance(function_argument, bool):
|
||||
raise Exception('Expected an RPC method name or visibility modifier!')
|
||||
else:
|
||||
@@ -222,12 +254,11 @@ class TimeoutException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@_add_metaclass(abc.ABCMeta)
|
||||
class Callback(object):
|
||||
'''
|
||||
A base class for callback handlers.
|
||||
'''
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
|
||||
@abc.abstractproperty
|
||||
def set_value(self, value):
|
||||
@@ -291,7 +322,7 @@ class Future(Callback):
|
||||
self._event.set()
|
||||
|
||||
|
||||
|
||||
@_add_metaclass(abc.ABCMeta)
|
||||
class Endpoint(object):
|
||||
'''
|
||||
The Endpoint class is an abstract base class for all objects
|
||||
@@ -303,8 +334,6 @@ class Endpoint(object):
|
||||
which specifies which object an RPC call refers to. It is the
|
||||
first part in a RPC method name '<fqn>.<method>'.
|
||||
'''
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
|
||||
def __init__(self, session, target_jid):
|
||||
'''
|
||||
@@ -491,7 +520,7 @@ class RemoteSession(object):
|
||||
|
||||
def _find_key(self, dict, value):
|
||||
"""return the key of dictionary dic given the value"""
|
||||
search = [k for k, v in dict.iteritems() if v == value]
|
||||
search = [k for k, v in dict.items() if v == value]
|
||||
if len(search) == 0:
|
||||
return None
|
||||
else:
|
||||
@@ -547,7 +576,7 @@ class RemoteSession(object):
|
||||
result = handler_cls(*args, **kwargs)
|
||||
Endpoint.__init__(result, self, self._client.boundjid.full)
|
||||
method_dict = result.get_methods()
|
||||
for method_name, method in method_dict.iteritems():
|
||||
for method_name, method in method_dict.items():
|
||||
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
|
||||
self._register_call(result.FQN(), method, method_name)
|
||||
self._register_acl(result.FQN(), acl)
|
||||
@@ -569,11 +598,11 @@ class RemoteSession(object):
|
||||
self._register_callback(pid, callback)
|
||||
iq.send()
|
||||
|
||||
def close(self):
|
||||
def close(self, wait=False):
|
||||
'''
|
||||
Closes this session.
|
||||
'''
|
||||
self._client.disconnect(False)
|
||||
self._client.disconnect(wait=wait)
|
||||
self._session_close_callback()
|
||||
|
||||
def _on_jabber_rpc_method_call(self, iq):
|
||||
@@ -697,7 +726,8 @@ class Remote(object):
|
||||
if(client.boundjid.bare in cls._sessions):
|
||||
raise RemoteException("There already is a session associated with these credentials!")
|
||||
else:
|
||||
cls._sessions[client.boundjid.bare] = client;
|
||||
cls._sessions[client.boundjid.bare] = client
|
||||
|
||||
def _session_close_callback():
|
||||
with Remote._lock:
|
||||
del cls._sessions[client.boundjid.bare]
|
||||
|
@@ -220,3 +220,4 @@ class XEP_0009(BasePlugin):
|
||||
def _extract_method(self, stanza):
|
||||
xml = ET.fromstring("%s" % stanza)
|
||||
return xml.find("./methodCall/methodName").text
|
||||
|
||||
|
@@ -609,7 +609,7 @@ class XEP_0030(BasePlugin):
|
||||
"""
|
||||
self.api['del_features'](jid, node, None, kwargs)
|
||||
|
||||
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
|
||||
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None):
|
||||
"""
|
||||
Execute the most specific node handler for the given
|
||||
JID/node combination.
|
||||
@@ -620,6 +620,9 @@ class XEP_0030(BasePlugin):
|
||||
node -- The node requested.
|
||||
data -- Optional, custom data to pass to the handler.
|
||||
"""
|
||||
if not data:
|
||||
data = {}
|
||||
|
||||
return self.api[htype](jid, node, ifrom, data)
|
||||
|
||||
def _handle_disco_info(self, iq):
|
||||
|
@@ -120,7 +120,7 @@ class DiscoInfo(ElementBase):
|
||||
id_xml.attrib['{%s}lang' % self.xml_ns] = lang
|
||||
if name:
|
||||
id_xml.attrib['name'] = name
|
||||
self.xml.append(id_xml)
|
||||
self.xml.insert(0, id_xml)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@@ -403,6 +403,16 @@ class XEP_0045(BasePlugin):
|
||||
return None
|
||||
return self.rooms[room].keys()
|
||||
|
||||
def getUsersByAffiliation(cls, room, affiliation='member', ifrom=None):
|
||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||
raise TypeError
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
||||
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
|
||||
query.append(item)
|
||||
iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
|
||||
iq.append(query)
|
||||
return iq.send()
|
||||
|
||||
|
||||
xep_0045 = XEP_0045
|
||||
register_plugin(XEP_0045)
|
||||
|
@@ -94,7 +94,7 @@ class XEP_0050(BasePlugin):
|
||||
self._handle_command))
|
||||
|
||||
register_stanza_plugin(Iq, Command)
|
||||
register_stanza_plugin(Command, Form)
|
||||
register_stanza_plugin(Command, Form, iterable=True)
|
||||
|
||||
self.xmpp.add_event_handler('command_execute',
|
||||
self._handle_command_start)
|
||||
@@ -415,12 +415,26 @@ class XEP_0050(BasePlugin):
|
||||
|
||||
del self.sessions[sessionid]
|
||||
|
||||
payload = session['payload']
|
||||
if payload is None:
|
||||
payload = []
|
||||
if not isinstance(payload, list):
|
||||
payload = [payload]
|
||||
|
||||
for item in payload:
|
||||
register_stanza_plugin(Command, item.__class__, iterable=True)
|
||||
|
||||
iq = iq.reply()
|
||||
|
||||
iq['command']['node'] = node
|
||||
iq['command']['sessionid'] = sessionid
|
||||
iq['command']['actions'] = []
|
||||
iq['command']['status'] = 'completed'
|
||||
iq['command']['notes'] = session['notes']
|
||||
|
||||
for item in payload:
|
||||
iq['command'].append(item)
|
||||
|
||||
iq.send()
|
||||
else:
|
||||
raise XMPPError('item-not-found')
|
||||
|
@@ -128,7 +128,8 @@ class Telephone(ElementBase):
|
||||
|
||||
def setup(self, xml=None):
|
||||
super(Telephone, self).setup(xml=xml)
|
||||
self._set_sub_text('NUMBER', '', keep=True)
|
||||
## this blanks out numbers received from server
|
||||
##self._set_sub_text('NUMBER', '', keep=True)
|
||||
|
||||
def set_number(self, value):
|
||||
self._set_sub_text('NUMBER', value, keep=True)
|
||||
@@ -324,7 +325,10 @@ class Birthday(ElementBase):
|
||||
def get_bday(self):
|
||||
if not self.xml.text:
|
||||
return None
|
||||
return xep_0082.parse(self.xml.text)
|
||||
try:
|
||||
return xep_0082.parse(self.xml.text)
|
||||
except ValueError:
|
||||
return self.xml.text
|
||||
|
||||
|
||||
class Rev(ElementBase):
|
||||
@@ -343,7 +347,10 @@ class Rev(ElementBase):
|
||||
def get_rev(self):
|
||||
if not self.xml.text:
|
||||
return None
|
||||
return xep_0082.parse(self.xml.text)
|
||||
try:
|
||||
return xep_0082.parse(self.xml.text)
|
||||
except ValueError:
|
||||
return self.xml.text
|
||||
|
||||
|
||||
class Title(ElementBase):
|
||||
@@ -523,8 +530,11 @@ class TimeZone(ElementBase):
|
||||
def get_tz(self):
|
||||
if not self.xml.text:
|
||||
return xep_0082.tzutc()
|
||||
time = xep_0082.parse('00:00:00%s' % self.xml.text)
|
||||
return time.tzinfo
|
||||
try:
|
||||
time = xep_0082.parse('00:00:00%s' % self.xml.text)
|
||||
return time.tzinfo
|
||||
except ValueError:
|
||||
return self.xml.text
|
||||
|
||||
|
||||
register_stanza_plugin(VCardTemp, Name)
|
||||
|
@@ -62,7 +62,7 @@ class XEP_0054(BasePlugin):
|
||||
|
||||
@future_wrapper
|
||||
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
|
||||
callback=None, timeout=None):
|
||||
callback=None, timeout=None, timeout_callback=None):
|
||||
if local is None:
|
||||
if jid is not None and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
@@ -101,11 +101,12 @@ class XEP_0054(BasePlugin):
|
||||
iq['type'] = 'get'
|
||||
iq.enable('vcard_temp')
|
||||
|
||||
return iq.send(callback=callback, timeout=timeout)
|
||||
return iq.send(callback=callback, timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
@future_wrapper
|
||||
def publish_vcard(self, vcard=None, jid=None, ifrom=None,
|
||||
callback=None, timeout=None):
|
||||
callback=None, timeout=None, timeout_callback=None):
|
||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
||||
if self.xmpp.is_component:
|
||||
return
|
||||
@@ -115,7 +116,8 @@ class XEP_0054(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq['type'] = 'set'
|
||||
iq.append(vcard)
|
||||
return iq.send(callback=callback, timeout=timeout)
|
||||
return iq.send(callback=callback, timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def _handle_get_vcard(self, iq):
|
||||
if iq['type'] == 'result':
|
||||
|
@@ -260,12 +260,12 @@ class XEP_0060(BasePlugin):
|
||||
|
||||
Arguments:
|
||||
jid -- The pubsub service JID.
|
||||
node -- The node to subscribe to.
|
||||
node -- The node to unsubscribe from.
|
||||
subid -- The specific subscription, if multiple subscriptions
|
||||
exist for this JID/node combination.
|
||||
bare -- Indicates if the subscribee is a bare or full JID.
|
||||
Defaults to True for a bare JID.
|
||||
subscribee -- The JID that is subscribing to the node.
|
||||
subscribee -- The JID that is unsubscribing from the node.
|
||||
ifrom -- Specify the sender's JID.
|
||||
timeout -- The length of time (in seconds) to wait for a
|
||||
response before exiting the send call if blocking
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
|
||||
from slixmpp.plugins.xep_0065.socks5 import Socks5Protocol
|
||||
from slixmpp.plugins.xep_0065.stanza import Socks5
|
||||
from slixmpp.plugins.xep_0065.proxy import XEP_0065
|
||||
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import socket
|
||||
|
||||
from hashlib import sha1
|
||||
from uuid import uuid4
|
||||
|
||||
from slixmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
|
||||
|
||||
from slixmpp.stanza import Iq
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
@@ -14,7 +12,7 @@ from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.plugins.base import BasePlugin
|
||||
|
||||
from slixmpp.plugins.xep_0065 import stanza, Socks5
|
||||
from slixmpp.plugins.xep_0065 import stanza, Socks5, Socks5Protocol
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -23,7 +21,7 @@ log = logging.getLogger(__name__)
|
||||
class XEP_0065(BasePlugin):
|
||||
|
||||
name = 'xep_0065'
|
||||
description = "Socks5 Bytestreams"
|
||||
description = "XEP-0065: SOCKS5 Bytestreams"
|
||||
dependencies = set(['xep_0030'])
|
||||
default_config = {
|
||||
'auto_accept': False
|
||||
@@ -34,9 +32,6 @@ class XEP_0065(BasePlugin):
|
||||
|
||||
self._proxies = {}
|
||||
self._sessions = {}
|
||||
self._sessions_lock = threading.Lock()
|
||||
|
||||
self._preauthed_sids_lock = threading.Lock()
|
||||
self._preauthed_sids = {}
|
||||
|
||||
self.xmpp.register_handler(
|
||||
@@ -65,32 +60,32 @@ class XEP_0065(BasePlugin):
|
||||
connection.
|
||||
"""
|
||||
if not self._proxies:
|
||||
self._proxies = self.discover_proxies()
|
||||
self._proxies = yield from self.discover_proxies()
|
||||
|
||||
if sid is None:
|
||||
sid = uuid4().hex
|
||||
|
||||
used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
|
||||
used = yield from 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,
|
||||
try:
|
||||
self._sessions[sid] = (yield from self._connect_proxy(
|
||||
self._get_dest_sha1(sid, self.xmpp.boundjid, to),
|
||||
self._proxies[proxy][0],
|
||||
self._proxies[proxy][1],
|
||||
peer=to)
|
||||
self._proxies[proxy][1]))[1]
|
||||
except socket.error:
|
||||
return None
|
||||
addr, port = yield from self._sessions[sid].connected
|
||||
|
||||
# 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
|
||||
yield from self.activate(proxy, sid, to, timeout=timeout)
|
||||
sock = self.get_socket(sid)
|
||||
self.xmpp.event('stream:%s:%s' % (sid, to), sock)
|
||||
return sock
|
||||
|
||||
def request_stream(self, to, sid=None, ifrom=None, timeout=None, callback=None):
|
||||
if sid is None:
|
||||
@@ -119,11 +114,16 @@ class XEP_0065(BasePlugin):
|
||||
|
||||
discovered = set()
|
||||
|
||||
disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
|
||||
disco_items = yield from self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
|
||||
disco_items = {item[0] for item in disco_items['disco_items']['items']}
|
||||
|
||||
for item in disco_items['disco_items']['items']:
|
||||
disco_info_futures = {}
|
||||
for item in disco_items:
|
||||
disco_info_futures[item] = self.xmpp['xep_0030'].get_info(item, timeout=timeout)
|
||||
|
||||
for item in disco_items:
|
||||
try:
|
||||
disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout)
|
||||
disco_info = yield from disco_info_futures[item]
|
||||
except XMPPError:
|
||||
continue
|
||||
else:
|
||||
@@ -135,7 +135,7 @@ class XEP_0065(BasePlugin):
|
||||
|
||||
for jid in discovered:
|
||||
try:
|
||||
addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
|
||||
addr = yield from self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
|
||||
self._proxies[jid] = (addr['socks']['streamhost']['host'],
|
||||
addr['socks']['streamhost']['port'])
|
||||
except XMPPError:
|
||||
@@ -149,6 +149,15 @@ class XEP_0065(BasePlugin):
|
||||
iq.enable('socks')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def _get_dest_sha1(self, sid, requester, target):
|
||||
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
|
||||
# where the output is hexadecimal-encoded (not binary).
|
||||
digest = sha1()
|
||||
digest.update(sid.encode('utf8'))
|
||||
digest.update(str(requester).encode('utf8'))
|
||||
digest.update(str(target).encode('utf8'))
|
||||
return digest.hexdigest()
|
||||
|
||||
def _handle_streamhost(self, iq):
|
||||
"""Handle incoming SOCKS5 session request."""
|
||||
sid = iq['socks']['sid']
|
||||
@@ -159,40 +168,59 @@ class XEP_0065(BasePlugin):
|
||||
raise XMPPError(etype='modify', condition='not-acceptable')
|
||||
|
||||
streamhosts = iq['socks']['streamhosts']
|
||||
conn = None
|
||||
used_streamhost = None
|
||||
requester = iq['from']
|
||||
target = iq['to']
|
||||
|
||||
sender = iq['from']
|
||||
dest = self._get_dest_sha1(sid, requester, target)
|
||||
|
||||
proxy_futures = []
|
||||
for streamhost in streamhosts:
|
||||
try:
|
||||
conn = self._connect_proxy(sid,
|
||||
sender,
|
||||
self.xmpp.boundjid,
|
||||
proxy_futures.append(self._connect_proxy(
|
||||
dest,
|
||||
streamhost['host'],
|
||||
streamhost['port'],
|
||||
peer=sender)
|
||||
used_streamhost = streamhost['jid']
|
||||
break
|
||||
except socket.error:
|
||||
continue
|
||||
else:
|
||||
raise XMPPError(etype='cancel', condition='item-not-found')
|
||||
streamhost['port']))
|
||||
|
||||
iq = iq.reply()
|
||||
with self._sessions_lock:
|
||||
@asyncio.coroutine
|
||||
def gather(futures, iq, streamhosts):
|
||||
proxies = yield from asyncio.gather(*futures, return_exceptions=True)
|
||||
for streamhost, proxy in zip(streamhosts, proxies):
|
||||
if isinstance(proxy, ValueError):
|
||||
continue
|
||||
elif isinstance(proxy, socket.error):
|
||||
log.error('Socket error while connecting to the proxy.')
|
||||
continue
|
||||
proxy = proxy[1]
|
||||
# TODO: what if the future never happens?
|
||||
try:
|
||||
addr, port = yield from proxy.connected
|
||||
except socket.error:
|
||||
log.exception('Socket error while connecting to the proxy.')
|
||||
continue
|
||||
# TODO: make a better choice than just the first working one.
|
||||
used_streamhost = streamhost['jid']
|
||||
conn = proxy
|
||||
break
|
||||
else:
|
||||
raise XMPPError(etype='cancel', condition='item-not-found')
|
||||
|
||||
# TODO: close properly the connection to the other proxies.
|
||||
|
||||
iq = iq.reply()
|
||||
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)
|
||||
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, requester), conn)
|
||||
|
||||
asyncio.async(gather(proxy_futures, iq, streamhosts))
|
||||
|
||||
def activate(self, proxy, sid, target, ifrom=None, 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(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def deactivate(self, sid):
|
||||
"""Closes the proxy socket associated with this SID."""
|
||||
@@ -204,66 +232,27 @@ class XEP_0065(BasePlugin):
|
||||
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]
|
||||
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 = {}
|
||||
self._sessions = {}
|
||||
|
||||
def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
|
||||
""" Establishes a connection between the client and the server-side
|
||||
def _connect_proxy(self, dest, proxy, proxy_port):
|
||||
""" Returns a future to 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.
|
||||
dest : The SHA-1 of (SID + Requester JID + Target JID), in hex. <str>
|
||||
host : The hostname or the IP of the proxy. <str>
|
||||
port : The port of the proxy. <str> or <int>
|
||||
"""
|
||||
# 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
|
||||
factory = lambda: Socks5Protocol(dest, 0, self.xmpp.event)
|
||||
return self.xmpp.loop.create_connection(factory, proxy, proxy_port)
|
||||
|
||||
def _accept_stream(self, iq):
|
||||
receiver = iq['to']
|
||||
@@ -278,15 +267,13 @@ class XEP_0065(BasePlugin):
|
||||
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
|
||||
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
|
||||
self._preauthed_sids[(jid, sid, ifrom)] = True
|
||||
|
265
slixmpp/plugins/xep_0065/socks5.py
Normal file
265
slixmpp/plugins/xep_0065/socks5.py
Normal file
@@ -0,0 +1,265 @@
|
||||
'''Pure asyncio implementation of RFC 1928 - SOCKS Protocol Version 5.'''
|
||||
|
||||
import asyncio
|
||||
import enum
|
||||
import logging
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from slixmpp.stringprep import punycode, StringprepError
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProtocolMismatch(Exception):
|
||||
'''We only implement SOCKS5, no other version or protocol.'''
|
||||
|
||||
|
||||
class ProtocolError(Exception):
|
||||
'''Some protocol error.'''
|
||||
|
||||
|
||||
class MethodMismatch(Exception):
|
||||
'''The server answered with a method we didn’t ask for.'''
|
||||
|
||||
|
||||
class MethodUnacceptable(Exception):
|
||||
'''None of our methods is supported by the server.'''
|
||||
|
||||
|
||||
class AddressTypeUnacceptable(Exception):
|
||||
'''The address type (ATYP) field isn’t one of IPv4, IPv6 or domain name.'''
|
||||
|
||||
|
||||
class ReplyError(Exception):
|
||||
'''The server answered with an error.'''
|
||||
|
||||
possible_values = (
|
||||
"succeeded",
|
||||
"general SOCKS server failure",
|
||||
"connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported",
|
||||
"Unknown error")
|
||||
|
||||
def __init__(self, result):
|
||||
if result < 9:
|
||||
Exception.__init__(self, self.possible_values[result])
|
||||
else:
|
||||
Exception.__init__(self, self.possible_values[9])
|
||||
|
||||
|
||||
class Method(enum.IntEnum):
|
||||
'''Known methods for a SOCKS5 session.'''
|
||||
none = 0
|
||||
gssapi = 1
|
||||
password = 2
|
||||
# Methods 3 to 127 are reserved by IANA.
|
||||
# Methods 128 to 254 are reserved for private use.
|
||||
unacceptable = 255
|
||||
not_yet_selected = -1
|
||||
|
||||
|
||||
class Command(enum.IntEnum):
|
||||
'''Existing commands for requests.'''
|
||||
connect = 1
|
||||
bind = 2
|
||||
udp_associate = 3
|
||||
|
||||
|
||||
class AddressType(enum.IntEnum):
|
||||
'''Existing address types.'''
|
||||
ipv4 = 1
|
||||
domain = 3
|
||||
ipv6 = 4
|
||||
|
||||
|
||||
class Socks5Protocol(asyncio.Protocol):
|
||||
'''This implements SOCKS5 as an asyncio protocol.'''
|
||||
|
||||
def __init__(self, dest_addr, dest_port, event):
|
||||
self.methods = {Method.none}
|
||||
self.selected_method = Method.not_yet_selected
|
||||
self.transport = None
|
||||
self.dest = (dest_addr, dest_port)
|
||||
self.connected = asyncio.Future()
|
||||
self.event = event
|
||||
self.paused = asyncio.Future()
|
||||
self.paused.set_result(None)
|
||||
|
||||
def register_method(self, method):
|
||||
'''Register a SOCKS5 method.'''
|
||||
self.methods.add(method)
|
||||
|
||||
def unregister_method(self, method):
|
||||
'''Unregister a SOCKS5 method.'''
|
||||
self.methods.remove(method)
|
||||
|
||||
def connection_made(self, transport):
|
||||
'''Called when the connection to the SOCKS5 server is established.'''
|
||||
|
||||
log.debug('SOCKS5 connection established.')
|
||||
|
||||
self.transport = transport
|
||||
self._send_methods()
|
||||
|
||||
def data_received(self, data):
|
||||
'''Called when we received some data from the SOCKS5 server.'''
|
||||
|
||||
log.debug('SOCKS5 message received.')
|
||||
|
||||
# If we are already connected, this is a data packet.
|
||||
if self.connected.done():
|
||||
return self.event('socks5_data', data)
|
||||
|
||||
# Every SOCKS5 message starts with the protocol version.
|
||||
if data[0] != 5:
|
||||
raise ProtocolMismatch()
|
||||
|
||||
# Then select the correct handler for the data we just received.
|
||||
if self.selected_method == Method.not_yet_selected:
|
||||
self._handle_method(data)
|
||||
else:
|
||||
self._handle_connect(data)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
log.debug('SOCKS5 connection closed.')
|
||||
self.event('socks5_closed', exc)
|
||||
|
||||
def pause_writing(self):
|
||||
self.paused = asyncio.Future()
|
||||
|
||||
def resume_writing(self):
|
||||
self.paused.set_result(None)
|
||||
|
||||
def write(self, data):
|
||||
yield from self.paused
|
||||
self.transport.write(data)
|
||||
|
||||
def _send_methods(self):
|
||||
'''Send the methods request, first thing a client should do.'''
|
||||
|
||||
# Create the buffer for our request.
|
||||
request = bytearray(len(self.methods) + 2)
|
||||
|
||||
# Protocol version.
|
||||
request[0] = 5
|
||||
|
||||
# Number of methods to send.
|
||||
request[1] = len(self.methods)
|
||||
|
||||
# List every method we support.
|
||||
for i, method in enumerate(self.methods):
|
||||
request[i + 2] = method
|
||||
|
||||
# Send the request.
|
||||
self.transport.write(request)
|
||||
|
||||
def _send_request(self, command):
|
||||
'''Send a request, should be done after having negociated a method.'''
|
||||
|
||||
# Encode the destination address to embed it in our request.
|
||||
# We need to do that first because its length is variable.
|
||||
address, port = self.dest
|
||||
addr = self._encode_addr(address)
|
||||
|
||||
# Create the buffer for our request.
|
||||
request = bytearray(5 + len(addr))
|
||||
|
||||
# Protocol version.
|
||||
request[0] = 5
|
||||
|
||||
# Specify the command we want to use.
|
||||
request[1] = command
|
||||
|
||||
# request[2] is reserved, keeping it at 0.
|
||||
|
||||
# Add our destination address and port.
|
||||
request[3:3+len(addr)] = addr
|
||||
request[-2:] = struct.pack('>H', port)
|
||||
|
||||
# Send the request.
|
||||
log.debug('SOCKS5 message sent.')
|
||||
self.transport.write(request)
|
||||
|
||||
def _handle_method(self, data):
|
||||
'''Handle a method reply from the server.'''
|
||||
|
||||
if len(data) != 2:
|
||||
raise ProtocolError()
|
||||
selected_method = data[1]
|
||||
if selected_method not in self.methods:
|
||||
raise MethodMismatch()
|
||||
if selected_method == Method.unacceptable:
|
||||
raise MethodUnacceptable()
|
||||
self.selected_method = selected_method
|
||||
self._send_request(Command.connect)
|
||||
|
||||
def _handle_connect(self, data):
|
||||
'''Handle a connect reply from the server.'''
|
||||
|
||||
try:
|
||||
addr, port = self._parse_result(data)
|
||||
except ReplyError as exception:
|
||||
self.connected.set_exception(exception)
|
||||
self.connected.set_result((addr, port))
|
||||
self.event('socks5_connected', (addr, port))
|
||||
|
||||
def _parse_result(self, data):
|
||||
'''Parse a reply from the server.'''
|
||||
|
||||
result = data[1]
|
||||
if result != 0:
|
||||
raise ReplyError(result)
|
||||
addr = self._parse_addr(data[3:-2])
|
||||
port = struct.unpack('>H', data[-2:])[0]
|
||||
return (addr, port)
|
||||
|
||||
@staticmethod
|
||||
def _parse_addr(addr):
|
||||
'''Parse an address (IP or domain) from a bytestream.'''
|
||||
|
||||
addr_type = addr[0]
|
||||
if addr_type == AddressType.ipv6:
|
||||
try:
|
||||
return socket.inet_ntop(socket.AF_INET6, addr[1:])
|
||||
except ValueError as e:
|
||||
raise AddressTypeUnacceptable(e)
|
||||
if addr_type == AddressType.ipv4:
|
||||
try:
|
||||
return socket.inet_ntop(socket.AF_INET, addr[1:])
|
||||
except ValueError as e:
|
||||
raise AddressTypeUnacceptable(e)
|
||||
if addr_type == AddressType.domain:
|
||||
length = addr[1]
|
||||
address = addr[2:]
|
||||
if length != len(address):
|
||||
raise Exception('Size mismatch')
|
||||
return address.decode()
|
||||
raise AddressTypeUnacceptable(addr_type)
|
||||
|
||||
@staticmethod
|
||||
def _encode_addr(addr):
|
||||
'''Encode an address (IP or domain) into a bytestream.'''
|
||||
|
||||
try:
|
||||
ipv6 = socket.inet_pton(socket.AF_INET6, addr)
|
||||
return b'\x04' + ipv6
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
ipv4 = socket.inet_aton(addr)
|
||||
return b'\x01' + ipv4
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
domain = punycode(addr)
|
||||
return b'\x03' + bytes([len(domain)]) + domain
|
||||
except StringprepError:
|
||||
pass
|
||||
raise Exception('Err…')
|
@@ -86,7 +86,8 @@ class XEP_0080(BasePlugin):
|
||||
ifrom = kwargs.get('ifrom', None)
|
||||
callback = kwargs.get('callback', None)
|
||||
timeout = kwargs.get('timeout', None)
|
||||
for param in ('ifrom', 'block', 'callback', 'timeout', 'options'):
|
||||
timeout_callback = kwargs.get('timeout_callback', None)
|
||||
for param in ('ifrom', 'block', 'callback', 'timeout', 'options', 'timeout_callback'):
|
||||
if param in kwargs:
|
||||
del kwargs[param]
|
||||
|
||||
@@ -97,9 +98,10 @@ class XEP_0080(BasePlugin):
|
||||
options=options,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def stop(self, ifrom=None, callback=None, timeout=None):
|
||||
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||
"""
|
||||
Clear existing user location information to stop notifications.
|
||||
|
||||
@@ -115,4 +117,5 @@ class XEP_0080(BasePlugin):
|
||||
return self.xmpp['xep_0163'].publish(geoloc,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=None)
|
||||
|
@@ -45,25 +45,28 @@ class XEP_0084(BasePlugin):
|
||||
return hashlib.sha1(data).hexdigest()
|
||||
|
||||
def retrieve_avatar(self, jid, id, url=None, ifrom=None,
|
||||
callback=None, timeout=None):
|
||||
callback=None, timeout=None, timeout_callback=None):
|
||||
return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def publish_avatar(self, data, ifrom=None, callback=None,
|
||||
timeout=None):
|
||||
timeout=None, timeout_callback=None):
|
||||
payload = Data()
|
||||
payload['value'] = data
|
||||
return self.xmpp['xep_0163'].publish(payload,
|
||||
id=self.generate_id(data),
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def publish_avatar_metadata(self, items=None, pointers=None,
|
||||
ifrom=None,
|
||||
callback=None, timeout=None):
|
||||
callback=None, timeout=None,
|
||||
timeout_callback=None):
|
||||
metadata = MetaData()
|
||||
if items is None:
|
||||
items = []
|
||||
@@ -83,9 +86,10 @@ class XEP_0084(BasePlugin):
|
||||
id=info['id'],
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def stop(self, ifrom=None, callback=None, timeout=None):
|
||||
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||
"""
|
||||
Clear existing avatar metadata information to stop notifications.
|
||||
|
||||
@@ -102,4 +106,5 @@ class XEP_0084(BasePlugin):
|
||||
node=MetaData.namespace,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
@@ -70,7 +70,8 @@ class XEP_0092(BasePlugin):
|
||||
iq['software_version']['os'] = self.os
|
||||
iq.send()
|
||||
|
||||
def get_version(self, jid, ifrom=None, timeout=None, callback=None):
|
||||
def get_version(self, jid, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
"""
|
||||
Retrieve the software version of a remote agent.
|
||||
|
||||
@@ -82,4 +83,5 @@ class XEP_0092(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq['type'] = 'get'
|
||||
iq['query'] = Version.namespace
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
@@ -47,6 +47,7 @@ class XEP_0096(BasePlugin):
|
||||
data['size'] = size
|
||||
data['date'] = date
|
||||
data['desc'] = desc
|
||||
data['hash'] = hash
|
||||
if allow_ranged:
|
||||
data.enable('range')
|
||||
|
||||
|
@@ -35,7 +35,7 @@ class XEP_0118(BasePlugin):
|
||||
|
||||
def publish_tune(self, artist=None, length=None, rating=None, source=None,
|
||||
title=None, track=None, uri=None, options=None,
|
||||
ifrom=None, callback=None, timeout=None):
|
||||
ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||
"""
|
||||
Publish the user's current tune.
|
||||
|
||||
@@ -68,9 +68,10 @@ class XEP_0118(BasePlugin):
|
||||
options=options,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def stop(self, ifrom=None, callback=None, timeout=None):
|
||||
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||
"""
|
||||
Clear existing user tune information to stop notifications.
|
||||
|
||||
@@ -87,4 +88,5 @@ class XEP_0118(BasePlugin):
|
||||
node=UserTune.namespace,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
11
slixmpp/plugins/xep_0122/__init__.py
Normal file
11
slixmpp/plugins/xep_0122/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
from slixmpp.plugins.xep_0122.stanza import FormValidation
|
||||
from slixmpp.plugins.xep_0122.data_validation import XEP_0122
|
||||
|
||||
|
||||
register_plugin(XEP_0122)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0122 = XEP_0122
|
19
slixmpp/plugins/xep_0122/data_validation.py
Normal file
19
slixmpp/plugins/xep_0122/data_validation.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.xep_0004 import stanza
|
||||
from slixmpp.plugins.xep_0004.stanza import FormField
|
||||
from slixmpp.plugins.xep_0122.stanza import FormValidation
|
||||
|
||||
|
||||
class XEP_0122(BasePlugin):
|
||||
"""
|
||||
XEP-0122: Data Forms
|
||||
"""
|
||||
|
||||
name = 'xep_0122'
|
||||
description = 'XEP-0122: Data Forms Validation'
|
||||
dependencies = set(['xep_0004'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(FormField, FormValidation)
|
93
slixmpp/plugins/xep_0122/stanza.py
Normal file
93
slixmpp/plugins/xep_0122/stanza.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from slixmpp.xmlstream import ElementBase, ET
|
||||
|
||||
|
||||
class FormValidation(ElementBase):
|
||||
"""
|
||||
Validation values for form fields.
|
||||
|
||||
Example:
|
||||
|
||||
<field var='evt.date' type='text-single' label='Event Date/Time'>
|
||||
<validate xmlns='http://jabber.org/protocol/xdata-validate'
|
||||
datatype='xs:dateTime'/>
|
||||
<value>2003-10-06T11:22:00-07:00</value>
|
||||
</field>
|
||||
|
||||
Questions:
|
||||
Should this look at the datatype value and convert the range values as appropriate?
|
||||
Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype?
|
||||
"""
|
||||
|
||||
namespace = 'http://jabber.org/protocol/xdata-validate'
|
||||
name = 'validate'
|
||||
plugin_attrib = 'validate'
|
||||
interfaces = {'datatype', 'basic', 'open', 'range', 'regex', }
|
||||
sub_interfaces = {'basic', 'open', 'range', 'regex', }
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def _add_field(self, name):
|
||||
self.remove_all()
|
||||
item_xml = ET.Element('{%s}%s' % (self.namespace, name))
|
||||
self.xml.append(item_xml)
|
||||
return item_xml
|
||||
|
||||
def set_basic(self, value):
|
||||
if value:
|
||||
self._add_field('basic')
|
||||
else:
|
||||
del self['basic']
|
||||
|
||||
def set_open(self, value):
|
||||
if value:
|
||||
self._add_field('open')
|
||||
else:
|
||||
del self['open']
|
||||
|
||||
def set_regex(self, regex):
|
||||
if regex:
|
||||
_regex = self._add_field('regex')
|
||||
_regex.text = regex
|
||||
else:
|
||||
del self['regex']
|
||||
|
||||
def set_range(self, value, minimum=None, maximum=None):
|
||||
if value:
|
||||
_range = self._add_field('range')
|
||||
_range.attrib['min'] = str(minimum)
|
||||
_range.attrib['max'] = str(maximum)
|
||||
else:
|
||||
del self['range']
|
||||
|
||||
def remove_all(self, except_tag=None):
|
||||
for a in self.sub_interfaces:
|
||||
if a != except_tag:
|
||||
del self[a]
|
||||
|
||||
def get_basic(self):
|
||||
present = self.xml.find('{%s}basic' % self.namespace)
|
||||
return present is not None
|
||||
|
||||
def get_open(self):
|
||||
present = self.xml.find('{%s}open' % self.namespace)
|
||||
return present is not None
|
||||
|
||||
def get_regex(self):
|
||||
present = self.xml.find('{%s}regex' % self.namespace)
|
||||
if present is not None:
|
||||
return present.text
|
||||
|
||||
return False
|
||||
|
||||
def get_range(self):
|
||||
present = self.xml.find('{%s}range' % self.namespace)
|
||||
if present is not None:
|
||||
attributes = present.attrib
|
||||
return_value = dict()
|
||||
if 'min' in attributes:
|
||||
return_value['minimum'] = attributes['min']
|
||||
if 'max' in attributes:
|
||||
return_value['maximum'] = attributes['max']
|
||||
return return_value
|
||||
|
||||
return False
|
145
slixmpp/plugins/xep_0138.py
Normal file
145
slixmpp/plugins/xep_0138.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
|
||||
from slixmpp.stanza import StreamFeatures
|
||||
from slixmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase
|
||||
from slixmpp.xmlstream.matcher import *
|
||||
from slixmpp.xmlstream.handler import *
|
||||
from slixmpp.plugins import BasePlugin, register_plugin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Compression(ElementBase):
|
||||
name = 'compression'
|
||||
namespace = 'http://jabber.org/features/compress'
|
||||
interfaces = set(('methods',))
|
||||
plugin_attrib = 'compression'
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
def get_methods(self):
|
||||
methods = []
|
||||
for method in self.xml.findall('{%s}method' % self.namespace):
|
||||
methods.append(method.text)
|
||||
return methods
|
||||
|
||||
|
||||
class Compress(StanzaBase):
|
||||
name = 'compress'
|
||||
namespace = 'http://jabber.org/protocol/compress'
|
||||
interfaces = set(('method',))
|
||||
sub_interfaces = interfaces
|
||||
plugin_attrib = 'compress'
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
def setup(self, xml):
|
||||
StanzaBase.setup(self, xml)
|
||||
self.xml.tag = self.tag_name()
|
||||
|
||||
|
||||
class Compressed(StanzaBase):
|
||||
name = 'compressed'
|
||||
namespace = 'http://jabber.org/protocol/compress'
|
||||
interfaces = set()
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
def setup(self, xml):
|
||||
StanzaBase.setup(self, xml)
|
||||
self.xml.tag = self.tag_name()
|
||||
|
||||
|
||||
|
||||
|
||||
class ZlibSocket(object):
|
||||
|
||||
def __init__(self, socketobj):
|
||||
self.__socket = socketobj
|
||||
self.compressor = zlib.compressobj()
|
||||
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__socket, name)
|
||||
|
||||
def send(self, data):
|
||||
sentlen = len(data)
|
||||
data = self.compressor.compress(data)
|
||||
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
|
||||
log.debug(b'>>> (compressed)' + (data.encode("hex")))
|
||||
#return self.__socket.send(data)
|
||||
sentactuallen = self.__socket.send(data)
|
||||
assert(sentactuallen == len(data))
|
||||
|
||||
return sentlen
|
||||
|
||||
def recv(self, *args, **kwargs):
|
||||
data = self.__socket.recv(*args, **kwargs)
|
||||
log.debug(b'<<< (compressed)' + data.encode("hex"))
|
||||
return self.decompressor.decompress(self.decompressor.unconsumed_tail + data)
|
||||
|
||||
|
||||
class XEP_0138(BasePlugin):
|
||||
"""
|
||||
XEP-0138: Compression
|
||||
"""
|
||||
name = "xep_0138"
|
||||
description = "XEP-0138: Compression"
|
||||
dependencies = set(["xep_0030"])
|
||||
|
||||
def plugin_init(self):
|
||||
self.xep = '0138'
|
||||
self.description = 'Stream Compression (Generic)'
|
||||
|
||||
self.compression_methods = {'zlib': True}
|
||||
|
||||
register_stanza_plugin(StreamFeatures, Compression)
|
||||
self.xmpp.register_stanza(Compress)
|
||||
self.xmpp.register_stanza(Compressed)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Compressed',
|
||||
StanzaPath('compressed'),
|
||||
self._handle_compressed,
|
||||
instream=True))
|
||||
|
||||
self.xmpp.register_feature('compression',
|
||||
self._handle_compression,
|
||||
restart=True,
|
||||
order=self.config.get('order', 5))
|
||||
|
||||
def register_compression_method(self, name, handler):
|
||||
self.compression_methods[name] = handler
|
||||
|
||||
def _handle_compression(self, features):
|
||||
for method in features['compression']['methods']:
|
||||
if method in self.compression_methods:
|
||||
log.info('Attempting to use %s compression' % method)
|
||||
c = Compress(self.xmpp)
|
||||
c['method'] = method
|
||||
c.send(now=True)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _handle_compressed(self, stanza):
|
||||
self.xmpp.features.add('compression')
|
||||
log.debug('Stream Compressed!')
|
||||
compressed_socket = ZlibSocket(self.xmpp.socket)
|
||||
self.xmpp.set_socket(compressed_socket)
|
||||
raise RestartStream()
|
||||
|
||||
def _handle_failure(self, stanza):
|
||||
pass
|
||||
|
||||
xep_0138 = XEP_0138
|
||||
register_plugin(XEP_0138)
|
@@ -33,8 +33,9 @@ class XEP_0152(BasePlugin):
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
|
||||
|
||||
def publish_reachability(self, addresses, options=None,
|
||||
ifrom=None, callback=None, timeout=None):
|
||||
def publish_reachability(self, addresses, options=None, ifrom=None,
|
||||
callback=None, timeout=None,
|
||||
timeout_callback=None):
|
||||
"""
|
||||
Publish alternative addresses where the user can be reached.
|
||||
|
||||
@@ -65,9 +66,10 @@ class XEP_0152(BasePlugin):
|
||||
options=options,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def stop(self, ifrom=None, callback=None, timeout=None):
|
||||
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||
"""
|
||||
Clear existing user activity information to stop notifications.
|
||||
|
||||
@@ -84,4 +86,5 @@ class XEP_0152(BasePlugin):
|
||||
node=Reachability.namespace,
|
||||
ifrom=ifrom,
|
||||
callback=callback,
|
||||
timeout=timeout)
|
||||
timeout=timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
|
@@ -59,7 +59,7 @@ class XEP_0153(BasePlugin):
|
||||
|
||||
@future_wrapper
|
||||
def set_avatar(self, jid=None, avatar=None, mtype=None, timeout=None,
|
||||
callback=None):
|
||||
callback=None, timeout_callback=None):
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.bare
|
||||
|
||||
@@ -79,7 +79,8 @@ class XEP_0153(BasePlugin):
|
||||
new_future = self.xmpp['xep_0054'].publish_vcard(jid=jid,
|
||||
vcard=vcard,
|
||||
timeout=timeout,
|
||||
callback=next_callback)
|
||||
callback=next_callback,
|
||||
timeout_callback=timeout_callback)
|
||||
new_future.add_done_callback(propagate_timeout_exception)
|
||||
|
||||
def next_callback(result):
|
||||
@@ -92,7 +93,8 @@ class XEP_0153(BasePlugin):
|
||||
future.set_result(result)
|
||||
|
||||
first_future = self.xmpp['xep_0054'].get_vcard(jid, cached=False, timeout=timeout,
|
||||
callback=custom_callback)
|
||||
callback=custom_callback,
|
||||
timeout_callback=timeout_callback)
|
||||
first_future.add_done_callback(propagate_timeout_exception)
|
||||
return future
|
||||
|
||||
|
@@ -45,14 +45,17 @@ class XEP_0191(BasePlugin):
|
||||
self.xmpp.remove_handler('Blocked Contact')
|
||||
self.xmpp.remove_handler('Unblocked Contact')
|
||||
|
||||
def get_blocked(self, ifrom=None, timeout=None, callback=None):
|
||||
def get_blocked(self, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('blocklist')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def block(self, jids, ifrom=None, timeout=None, callback=None):
|
||||
def block(self, jids, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
@@ -61,9 +64,11 @@ class XEP_0191(BasePlugin):
|
||||
jids = [jids]
|
||||
|
||||
iq['block']['items'] = jids
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def unblock(self, jids=None, ifrom=None, timeout=None, callback=None):
|
||||
def unblock(self, jids=None, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
@@ -74,7 +79,8 @@ class XEP_0191(BasePlugin):
|
||||
jids = [jids]
|
||||
|
||||
iq['unblock']['items'] = jids
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def _handle_blocked(self, iq):
|
||||
self.xmpp.event('blocked', iq)
|
||||
|
@@ -96,3 +96,4 @@ class XEP_0202(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq.enable('entity_time')
|
||||
return iq.send(**iqargs)
|
||||
|
||||
|
@@ -134,8 +134,7 @@ class XEP_0231(BasePlugin):
|
||||
def _get_bob(self, jid, node, ifrom, cid):
|
||||
if cid in self._cids:
|
||||
return self._cids[cid]
|
||||
else:
|
||||
raise XMPPError('item-not-found')
|
||||
raise XMPPError('item-not-found')
|
||||
|
||||
def _del_bob(self, jid, node, ifrom, cid):
|
||||
if cid in self._cids:
|
||||
|
@@ -21,10 +21,10 @@ class BitsOfBinary(ElementBase):
|
||||
interfaces = set(('cid', 'max_age', 'type', 'data'))
|
||||
|
||||
def get_max_age(self):
|
||||
return self._get_attr('max-age')
|
||||
return int(self._get_attr('max-age'))
|
||||
|
||||
def set_max_age(self, value):
|
||||
self._set_attr('max-age', value)
|
||||
self._set_attr('max-age', str(value))
|
||||
|
||||
def get_data(self):
|
||||
return base64.b64decode(bytes(self.xml.text))
|
||||
|
@@ -31,35 +31,40 @@ class XEP_0257(BasePlugin):
|
||||
register_stanza_plugin(Iq, DisableCert)
|
||||
register_stanza_plugin(Iq, RevokeCert)
|
||||
|
||||
def get_certs(self, ifrom=None, timeout=None, callback=None):
|
||||
def get_certs(self, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('sasl_certs')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def add_cert(self, name, cert, allow_management=True, ifrom=None,
|
||||
timeout=None, callback=None):
|
||||
timeout=None, callback=None, timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['sasl_cert_append']['name'] = name
|
||||
iq['sasl_cert_append']['x509cert'] = cert
|
||||
iq['sasl_cert_append']['cert_management'] = allow_management
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def disable_cert(self, name, ifrom=None,
|
||||
timeout=None, callback=None):
|
||||
def disable_cert(self, name, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['sasl_cert_disable']['name'] = name
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
def revoke_cert(self, name, ifrom=None,
|
||||
timeout=None, callback=None):
|
||||
def revoke_cert(self, name, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['sasl_cert_revoke']['name'] = name
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
@@ -31,9 +31,11 @@ class XEP_0279(BasePlugin):
|
||||
def plugin_end(self):
|
||||
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0')
|
||||
|
||||
def check_ip(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||
def check_ip(self, ifrom=None, block=True, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('ip_check')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
return iq.send(block=block, timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
@@ -21,7 +21,10 @@ class Device(object):
|
||||
request_fields
|
||||
"""
|
||||
|
||||
def __init__(self, nodeId, fields={}):
|
||||
def __init__(self, nodeId, fields=None):
|
||||
if not fields:
|
||||
fields = {}
|
||||
|
||||
self.nodeId = nodeId
|
||||
self.fields = fields # see fields described below
|
||||
# {'type':'numeric',
|
||||
|
@@ -22,7 +22,6 @@ from slixmpp.plugins.base import BasePlugin
|
||||
from slixmpp.plugins.xep_0323 import stanza
|
||||
from slixmpp.plugins.xep_0323.stanza import Sensordata
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -108,7 +107,6 @@ class XEP_0323(BasePlugin):
|
||||
|
||||
default_config = {
|
||||
'threaded': True
|
||||
# 'session_db': None
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
@@ -161,11 +159,11 @@ class XEP_0323(BasePlugin):
|
||||
self.last_seqnr = 0
|
||||
self.seqnr_lock = Lock()
|
||||
|
||||
## For testning only
|
||||
## For testing only
|
||||
self.test_authenticated_from = ""
|
||||
|
||||
def post_init(self):
|
||||
""" Init complete. Register our features in Serivce discovery. """
|
||||
""" Init complete. Register our features in Service discovery. """
|
||||
BasePlugin.post_init(self)
|
||||
self.xmpp['xep_0030'].add_feature(Sensordata.namespace)
|
||||
self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple())
|
||||
@@ -301,8 +299,6 @@ class XEP_0323(BasePlugin):
|
||||
self.sessions[session]["commTimers"] = {}
|
||||
self.sessions[session]["nodeDone"] = {}
|
||||
|
||||
#print("added session: " + str(self.sessions))
|
||||
|
||||
iq = iq.reply()
|
||||
iq['accepted']['seqnr'] = seqnr
|
||||
if not request_delay_sec is None:
|
||||
@@ -319,10 +315,8 @@ class XEP_0323(BasePlugin):
|
||||
return
|
||||
|
||||
if self.threaded:
|
||||
#print("starting thread")
|
||||
tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags))
|
||||
tr_req.start()
|
||||
#print("started thread")
|
||||
else:
|
||||
self._threaded_node_request(session, process_fields, req_flags)
|
||||
|
||||
@@ -349,7 +343,6 @@ class XEP_0323(BasePlugin):
|
||||
for node in self.sessions[session]["node_list"]:
|
||||
timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node))
|
||||
self.sessions[session]["commTimers"][node] = timer
|
||||
#print("Starting timer " + str(timer) + ", timeout: " + str(self.nodes[node]['commTimeout']))
|
||||
timer.start()
|
||||
self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback)
|
||||
|
||||
@@ -377,7 +370,6 @@ class XEP_0323(BasePlugin):
|
||||
msg['failure']['done'] = 'true'
|
||||
msg.send()
|
||||
# The session is complete, delete it
|
||||
#print("del session " + session + " due to timeout")
|
||||
del self.sessions[session]
|
||||
|
||||
def _event_delayed_req(self, session, process_fields, req_flags):
|
||||
@@ -404,7 +396,7 @@ class XEP_0323(BasePlugin):
|
||||
|
||||
def _all_nodes_done(self, session):
|
||||
"""
|
||||
Checks wheter all devices are done replying to the readout.
|
||||
Checks whether all devices are done replying to the readout.
|
||||
|
||||
Arguments:
|
||||
session -- The request session id
|
||||
@@ -448,7 +440,7 @@ class XEP_0323(BasePlugin):
|
||||
Error details when a request failed.
|
||||
"""
|
||||
if not session in self.sessions:
|
||||
# This can happend if a session was deleted, like in a cancellation. Just drop the data.
|
||||
# This can happen if a session was deleted, like in a cancellation. Just drop the data.
|
||||
return
|
||||
|
||||
if result == "error":
|
||||
@@ -467,7 +459,6 @@ class XEP_0323(BasePlugin):
|
||||
if (self._all_nodes_done(session)):
|
||||
msg['failure']['done'] = 'true'
|
||||
# The session is complete, delete it
|
||||
# print("del session " + session + " due to error")
|
||||
del self.sessions[session]
|
||||
msg.send()
|
||||
else:
|
||||
@@ -491,11 +482,10 @@ class XEP_0323(BasePlugin):
|
||||
if result == "done":
|
||||
self.sessions[session]["commTimers"][nodeId].cancel()
|
||||
self.sessions[session]["nodeDone"][nodeId] = True
|
||||
msg['fields']['done'] = 'true'
|
||||
if (self._all_nodes_done(session)):
|
||||
# The session is complete, delete it
|
||||
# print("del session " + session + " due to complete")
|
||||
del self.sessions[session]
|
||||
msg['fields']['done'] = 'true'
|
||||
else:
|
||||
# Restart comm timer
|
||||
self.sessions[session]["commTimers"][nodeId].reset()
|
||||
@@ -531,19 +521,19 @@ class XEP_0323(BasePlugin):
|
||||
iq['rejected']['error'] = "Cancel request received, no matching request is active."
|
||||
iq.send()
|
||||
|
||||
# =================================================================
|
||||
# =================================================================
|
||||
# Client side (data retriever) API
|
||||
|
||||
def request_data(self, from_jid, to_jid, callback, nodeIds=None, fields=None, flags=None):
|
||||
"""
|
||||
Called on the client side to initiade a data readout.
|
||||
Called on the client side to initiate a data readout.
|
||||
Composes a message with the request and sends it to the device(s).
|
||||
Does not block, the callback will be called when data is available.
|
||||
|
||||
Arguments:
|
||||
from_jid -- The jid of the requester
|
||||
to_jid -- The jid of the device(s)
|
||||
callback -- The callback function to call when data is availble.
|
||||
callback -- The callback function to call when data is available.
|
||||
|
||||
The callback function must support the following arguments:
|
||||
|
||||
@@ -636,7 +626,7 @@ class XEP_0323(BasePlugin):
|
||||
def _get_new_seqnr(self):
|
||||
""" Returns a unique sequence number (unique across threads) """
|
||||
self.seqnr_lock.acquire()
|
||||
self.last_seqnr = self.last_seqnr + 1
|
||||
self.last_seqnr += 1
|
||||
self.seqnr_lock.release()
|
||||
return str(self.last_seqnr)
|
||||
|
||||
@@ -664,7 +654,6 @@ class XEP_0323(BasePlugin):
|
||||
Received Iq with cancelled - this is a cancel confirm.
|
||||
Delete the session.
|
||||
"""
|
||||
#print("Got cancelled")
|
||||
seqnr = iq['cancelled']['seqnr']
|
||||
callback = self.sessions[seqnr]["callback"]
|
||||
callback(from_jid=iq['from'], result="cancelled")
|
||||
@@ -673,7 +662,7 @@ class XEP_0323(BasePlugin):
|
||||
|
||||
def _handle_event_fields(self, msg):
|
||||
"""
|
||||
Received Msg with fields - this is a data reponse to a request.
|
||||
Received Msg with fields - this is a data response to a request.
|
||||
If this is the last data block, issue a "done" callback.
|
||||
"""
|
||||
seqnr = msg['fields']['seqnr']
|
||||
|
@@ -23,7 +23,12 @@ class _TimerReset(Thread):
|
||||
t.cancel() # stop the timer's action if it's still waiting
|
||||
"""
|
||||
|
||||
def __init__(self, interval, function, args=[], kwargs={}):
|
||||
def __init__(self, interval, function, args=None, kwargs=None):
|
||||
if not kwargs:
|
||||
kwargs = {}
|
||||
if not args:
|
||||
args = []
|
||||
|
||||
Thread.__init__(self)
|
||||
self.interval = interval
|
||||
self.function = function
|
||||
|
@@ -223,7 +223,6 @@ class XEP_0325(BasePlugin):
|
||||
error_msg = "Access denied"
|
||||
|
||||
# Nodes
|
||||
process_nodes = []
|
||||
if len(iq['set']['nodes']) > 0:
|
||||
for n in iq['set']['nodes']:
|
||||
if not n['nodeId'] in self.nodes:
|
||||
@@ -286,7 +285,6 @@ class XEP_0325(BasePlugin):
|
||||
req_ok = True
|
||||
|
||||
# Nodes
|
||||
process_nodes = []
|
||||
if len(msg['set']['nodes']) > 0:
|
||||
for n in msg['set']['nodes']:
|
||||
if not n['nodeId'] in self.nodes:
|
||||
@@ -548,4 +546,3 @@ class XEP_0325(BasePlugin):
|
||||
callback = self.sessions[seqnr]["callback"]
|
||||
callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg)
|
||||
|
||||
|
||||
|
17
slixmpp/plugins/xep_0332/__init__.py
Normal file
17
slixmpp/plugins/xep_0332/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
|
||||
from slixmpp.plugins.xep_0332 import stanza
|
||||
from slixmpp.plugins.xep_0332.http import XEP_0332
|
||||
|
||||
|
||||
register_plugin(XEP_0332)
|
159
slixmpp/plugins/xep_0332/http.py
Normal file
159
slixmpp/plugins/xep_0332/http.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from slixmpp import Iq
|
||||
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
|
||||
from slixmpp.plugins.base import BasePlugin
|
||||
from slixmpp.plugins.xep_0332.stanza import (
|
||||
HTTPRequest, HTTPResponse, HTTPData
|
||||
)
|
||||
from slixmpp.plugins.xep_0131.stanza import Headers
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0332(BasePlugin):
|
||||
"""
|
||||
XEP-0332: HTTP over XMPP transport
|
||||
"""
|
||||
|
||||
name = 'xep_0332'
|
||||
description = 'XEP-0332: HTTP over XMPP transport'
|
||||
|
||||
#: xep_0047 not included.
|
||||
#: xep_0001, 0137 and 0166 are missing
|
||||
dependencies = set(['xep_0030', 'xep_0131'])
|
||||
|
||||
#: TODO: Do we really need to mention the supported_headers?!
|
||||
default_config = {
|
||||
'supported_headers': set([
|
||||
'Content-Length', 'Transfer-Encoding', 'DateTime',
|
||||
'Accept-Charset', 'Location', 'Content-ID', 'Description',
|
||||
'Content-Language', 'Content-Transfer-Encoding', 'Timestamp',
|
||||
'Expires', 'User-Agent', 'Host', 'Proxy-Authorization', 'Date',
|
||||
'WWW-Authenticate', 'Accept-Encoding', 'Server', 'Error-Info',
|
||||
'Identifier', 'Content-Location', 'Content-Encoding', 'Distribute',
|
||||
'Accept', 'Proxy-Authenticate', 'ETag', 'Expect', 'Content-Type'
|
||||
])
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.register_handler(
|
||||
Callback(
|
||||
'HTTP Request',
|
||||
StanzaPath('iq/http-req'),
|
||||
self._handle_request
|
||||
)
|
||||
)
|
||||
self.xmpp.register_handler(
|
||||
Callback(
|
||||
'HTTP Response',
|
||||
StanzaPath('iq/http-resp'),
|
||||
self._handle_response
|
||||
)
|
||||
)
|
||||
register_stanza_plugin(Iq, HTTPRequest, iterable=True)
|
||||
register_stanza_plugin(Iq, HTTPResponse, iterable=True)
|
||||
register_stanza_plugin(HTTPRequest, Headers, iterable=True)
|
||||
register_stanza_plugin(HTTPRequest, HTTPData, iterable=True)
|
||||
register_stanza_plugin(HTTPResponse, Headers, iterable=True)
|
||||
register_stanza_plugin(HTTPResponse, HTTPData, iterable=True)
|
||||
# TODO: Should we register any api's here? self.api.register()
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('HTTP Request')
|
||||
self.xmpp.remove_handler('HTTP Response')
|
||||
self.xmpp['xep_0030'].del_feature('urn:xmpp:http')
|
||||
for header in self.supported_headers:
|
||||
self.xmpp['xep_0030'].del_feature(
|
||||
feature='%s#%s' % (Headers.namespace, header)
|
||||
)
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature('urn:xmpp:http')
|
||||
for header in self.supported_headers:
|
||||
self.xmpp['xep_0030'].add_feature(
|
||||
'%s#%s' % (Headers.namespace, header)
|
||||
)
|
||||
# TODO: Do we need to add the supported headers to xep_0131?
|
||||
# self.xmpp['xep_0131'].supported_headers.add(header)
|
||||
|
||||
def _handle_request(self, iq):
|
||||
self.xmpp.event('http_request', iq)
|
||||
|
||||
def _handle_response(self, iq):
|
||||
self.xmpp.event('http_response', iq)
|
||||
|
||||
def send_request(self, to=None, method=None, resource=None, headers=None,
|
||||
data=None, **kwargs):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['from'] = self.xmpp.boundjid
|
||||
iq['to'] = to
|
||||
iq['type'] = 'set'
|
||||
iq['http-req']['headers'] = headers
|
||||
iq['http-req']['method'] = method
|
||||
iq['http-req']['resource'] = resource
|
||||
iq['http-req']['version'] = '1.1' # TODO: set this implicitly
|
||||
if 'id' in kwargs:
|
||||
iq['id'] = kwargs["id"]
|
||||
if data is not None:
|
||||
iq['http-req']['data'] = data
|
||||
return iq.send(
|
||||
timeout=kwargs.get('timeout', None),
|
||||
block=kwargs.get('block', True),
|
||||
callback=kwargs.get('callback', None),
|
||||
timeout_callback=kwargs.get('timeout_callback', None)
|
||||
)
|
||||
|
||||
def send_response(self, to=None, code=None, message=None, headers=None,
|
||||
data=None, **kwargs):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['from'] = self.xmpp.boundjid
|
||||
iq['to'] = to
|
||||
iq['type'] = 'result'
|
||||
iq['http-resp']['headers'] = headers
|
||||
iq['http-resp']['code'] = code
|
||||
iq['http-resp']['message'] = message
|
||||
iq['http-resp']['version'] = '1.1' # TODO: set this implicitly
|
||||
if 'id' in kwargs:
|
||||
iq['id'] = kwargs["id"]
|
||||
if data is not None:
|
||||
iq['http-resp']['data'] = data
|
||||
return iq.send(
|
||||
timeout=kwargs.get('timeout', None),
|
||||
block=kwargs.get('block', True),
|
||||
callback=kwargs.get('callback', None),
|
||||
timeout_callback=kwargs.get('timeout_callback', None)
|
||||
)
|
||||
|
||||
def send_error(self, to=None, ecode='500', etype='wait',
|
||||
econd='internal-server-error', **kwargs):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['from'] = self.xmpp.boundjid
|
||||
iq['to'] = to
|
||||
iq['type'] = 'error'
|
||||
iq['error']['code'] = ecode
|
||||
iq['error']['type'] = etype
|
||||
iq['error']['condition'] = econd
|
||||
if 'id' in kwargs:
|
||||
iq['id'] = kwargs["id"]
|
||||
return iq.send(
|
||||
timeout=kwargs.get('timeout', None),
|
||||
block=kwargs.get('block', True),
|
||||
callback=kwargs.get('callback', None),
|
||||
timeout_callback=kwargs.get('timeout_callback', None)
|
||||
)
|
13
slixmpp/plugins/xep_0332/stanza/__init__.py
Normal file
13
slixmpp/plugins/xep_0332/stanza/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.xep_0332.stanza.request import HTTPRequest
|
||||
from slixmpp.plugins.xep_0332.stanza.response import HTTPResponse
|
||||
from slixmpp.plugins.xep_0332.stanza.data import HTTPData
|
30
slixmpp/plugins/xep_0332/stanza/data.py
Normal file
30
slixmpp/plugins/xep_0332/stanza/data.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class HTTPData(ElementBase):
|
||||
"""
|
||||
The data element.
|
||||
"""
|
||||
name = 'data'
|
||||
namespace = 'urn:xmpp:http'
|
||||
interfaces = set(['data'])
|
||||
plugin_attrib = 'data'
|
||||
is_extension = True
|
||||
|
||||
def get_data(self, encoding='text'):
|
||||
data = self._get_sub_text(encoding, None)
|
||||
return str(data) if data is not None else data
|
||||
|
||||
def set_data(self, data, encoding='text'):
|
||||
self._set_sub_text(encoding, text=data)
|
||||
|
71
slixmpp/plugins/xep_0332/stanza/request.py
Normal file
71
slixmpp/plugins/xep_0332/stanza/request.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class HTTPRequest(ElementBase):
|
||||
|
||||
"""
|
||||
All HTTP communication is done using the `Request`/`Response` paradigm.
|
||||
Each HTTP Request is made sending an `iq` stanza containing a `req`
|
||||
element to the server. Each `iq` stanza sent is of type `set`.
|
||||
|
||||
Examples:
|
||||
<iq type='set' from='a@b.com/browser' to='x@y.com' id='1'>
|
||||
<req xmlns='urn:xmpp:http'
|
||||
method='GET'
|
||||
resource='/api/users'
|
||||
version='1.1'>
|
||||
<headers xmlns='http://jabber.org/protocol/shim'>
|
||||
<header name='Host'>b.com</header>
|
||||
</headers>
|
||||
</req>
|
||||
</iq>
|
||||
|
||||
<iq type='set' from='a@b.com/browser' to='x@y.com' id='2'>
|
||||
<req xmlns='urn:xmpp:http'
|
||||
method='PUT'
|
||||
resource='/api/users'
|
||||
version='1.1'>
|
||||
<headers xmlns='http://jabber.org/protocol/shim'>
|
||||
<header name='Host'>b.com</header>
|
||||
<header name='Content-Type'>text/html</header>
|
||||
<header name='Content-Length'>...</header>
|
||||
</headers>
|
||||
<data>
|
||||
<text>...</text>
|
||||
</data>
|
||||
</req>
|
||||
</iq>
|
||||
"""
|
||||
|
||||
name = 'request'
|
||||
namespace = 'urn:xmpp:http'
|
||||
interfaces = set(['method', 'resource', 'version'])
|
||||
plugin_attrib = 'http-req'
|
||||
|
||||
def get_method(self):
|
||||
return self._get_attr('method', None)
|
||||
|
||||
def set_method(self, method):
|
||||
self._set_attr('method', method)
|
||||
|
||||
def get_resource(self):
|
||||
return self._get_attr('resource', None)
|
||||
|
||||
def set_resource(self, resource):
|
||||
self._set_attr('resource', resource)
|
||||
|
||||
def get_version(self):
|
||||
return self._get_attr('version', None)
|
||||
|
||||
def set_version(self, version='1.1'):
|
||||
self._set_attr('version', version)
|
66
slixmpp/plugins/xep_0332/stanza/response.py
Normal file
66
slixmpp/plugins/xep_0332/stanza/response.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class HTTPResponse(ElementBase):
|
||||
|
||||
"""
|
||||
When the HTTP Server responds, it does so by sending an `iq` stanza
|
||||
response (type=`result`) back to the client containing the `resp` element.
|
||||
Since response are asynchronous, and since multiple requests may be active
|
||||
at the same time, responses may be returned in a different order than the
|
||||
in which the original requests were made.
|
||||
|
||||
Examples:
|
||||
<iq type='result'
|
||||
from='httpserver@clayster.com'
|
||||
to='httpclient@clayster.com/browser' id='2'>
|
||||
<resp xmlns='urn:xmpp:http'
|
||||
version='1.1'
|
||||
statusCode='200'
|
||||
statusMessage='OK'>
|
||||
<headers xmlns='http://jabber.org/protocol/shim'>
|
||||
<header name='Date'>Fri, 03 May 2013 16:39:54GMT-4</header>
|
||||
<header name='Server'>Clayster</header>
|
||||
<header name='Content-Type'>text/turtle</header>
|
||||
<header name='Content-Length'>...</header>
|
||||
<header name='Connection'>Close</header>
|
||||
</headers>
|
||||
<data>
|
||||
<text>
|
||||
...
|
||||
</text>
|
||||
</data>
|
||||
</resp>
|
||||
</iq>
|
||||
"""
|
||||
|
||||
name = 'response'
|
||||
namespace = 'urn:xmpp:http'
|
||||
interfaces = set(['code', 'message', 'version'])
|
||||
plugin_attrib = 'http-resp'
|
||||
|
||||
def get_code(self):
|
||||
code = self._get_attr('statusCode', None)
|
||||
return int(code) if code is not None else code
|
||||
|
||||
def set_code(self, code):
|
||||
self._set_attr('statusCode', str(code))
|
||||
|
||||
def get_message(self):
|
||||
return self._get_attr('statusMessage', '')
|
||||
|
||||
def set_message(self, message):
|
||||
self._set_attr('statusMessage', message)
|
||||
|
||||
def set_version(self, version='1.1'):
|
||||
self._set_attr('version', version)
|
@@ -254,6 +254,9 @@ class RosterNode(object):
|
||||
callback -- Optional reference to a stream handler function.
|
||||
Will be executed when the roster is received.
|
||||
"""
|
||||
if not groups:
|
||||
groups = []
|
||||
|
||||
self[jid]['name'] = name
|
||||
self[jid]['groups'] = groups
|
||||
self[jid].save()
|
||||
|
@@ -6,8 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
class AtomEntry(ElementBase):
|
||||
|
||||
@@ -22,5 +21,23 @@ class AtomEntry(ElementBase):
|
||||
namespace = 'http://www.w3.org/2005/Atom'
|
||||
name = 'entry'
|
||||
plugin_attrib = 'entry'
|
||||
interfaces = set(('title', 'summary'))
|
||||
sub_interfaces = set(('title', 'summary'))
|
||||
interfaces = set(('title', 'summary', 'id', 'published', 'updated'))
|
||||
sub_interfaces = set(('title', 'summary', 'id', 'published',
|
||||
'updated'))
|
||||
|
||||
class AtomAuthor(ElementBase):
|
||||
|
||||
"""
|
||||
An Atom author.
|
||||
|
||||
Stanza Interface:
|
||||
name -- The printable author name
|
||||
uri -- The bare jid of the author
|
||||
"""
|
||||
|
||||
name = 'author'
|
||||
plugin_attrib = 'author'
|
||||
interfaces = set(('name', 'uri'))
|
||||
sub_interfaces = set(('name', 'uri'))
|
||||
|
||||
register_stanza_plugin(AtomEntry, AtomAuthor)
|
||||
|
@@ -60,7 +60,9 @@ class RootStanza(StanzaBase):
|
||||
reply.send()
|
||||
elif isinstance(e, XMPPError):
|
||||
# We raised this deliberately
|
||||
keep_id = self['id']
|
||||
reply = self.reply(clear=e.clear)
|
||||
reply['id'] = keep_id
|
||||
reply['error']['condition'] = e.condition
|
||||
reply['error']['text'] = e.text
|
||||
reply['error']['type'] = e.etype
|
||||
@@ -72,7 +74,9 @@ class RootStanza(StanzaBase):
|
||||
reply.send()
|
||||
else:
|
||||
# We probably didn't raise this on purpose, so send an error stanza
|
||||
keep_id = self['id']
|
||||
reply = self.reply()
|
||||
reply['id'] = keep_id
|
||||
reply['error']['condition'] = 'undefined-condition'
|
||||
reply['error']['text'] = "Slixmpp got into trouble."
|
||||
reply['error']['type'] = 'cancel'
|
||||
|
@@ -101,5 +101,21 @@ def idna(domain):
|
||||
domain_parts.append(label)
|
||||
return '.'.join(domain_parts)
|
||||
|
||||
def punycode(domain):
|
||||
domain_parts = []
|
||||
for label in domain.split('.'):
|
||||
try:
|
||||
label = encodings.idna.nameprep(label)
|
||||
encodings.idna.ToASCII(label)
|
||||
except UnicodeError:
|
||||
raise StringprepError
|
||||
|
||||
for char in label:
|
||||
if char in ILLEGAL_CHARS:
|
||||
raise StringprepError
|
||||
|
||||
domain_parts.append(label)
|
||||
return b'.'.join(domain_parts)
|
||||
|
||||
logging.getLogger(__name__).warning('Using slower stringprep, consider '
|
||||
'compiling the faster cython/libidn one.')
|
||||
|
@@ -19,7 +19,8 @@ from libc.stdlib cimport free
|
||||
# Those are Cython declarations for the C function we’ll be using.
|
||||
|
||||
cdef extern from "stringprep.h" nogil:
|
||||
int stringprep_profile(const char* in_, char** out, const char* profile, int flags)
|
||||
int stringprep_profile(const char* in_, char** out, const char* profile,
|
||||
int flags)
|
||||
|
||||
cdef extern from "idna.h" nogil:
|
||||
int idna_to_ascii_8z(const char* in_, char** out, int flags)
|
||||
@@ -40,16 +41,19 @@ cdef str _stringprep(str in_, const char* profile):
|
||||
free(out)
|
||||
return unicode_out
|
||||
|
||||
|
||||
def nodeprep(str node):
|
||||
"""The nodeprep profile of stringprep used to validate the local, or
|
||||
username, portion of a JID."""
|
||||
return _stringprep(node, 'Nodeprep')
|
||||
|
||||
|
||||
def resourceprep(str resource):
|
||||
"""The resourceprep profile of stringprep, which is used to validate the
|
||||
resource portion of a JID."""
|
||||
return _stringprep(resource, 'Resourceprep')
|
||||
|
||||
|
||||
def idna(str domain):
|
||||
"""The idna conversion functions, which are used to validate the domain
|
||||
portion of a JID."""
|
||||
@@ -69,3 +73,17 @@ def idna(str domain):
|
||||
unicode_domain = utf8_domain.decode('utf-8')
|
||||
free(utf8_domain)
|
||||
return unicode_domain
|
||||
|
||||
|
||||
def punycode(str domain):
|
||||
"""Converts a domain name to its punycode representation."""
|
||||
|
||||
cdef char* ascii_domain
|
||||
cdef bytes bytes_domain
|
||||
|
||||
ret = idna_to_ascii_8z(domain.encode('utf-8'), &ascii_domain, 0)
|
||||
if ret != 0:
|
||||
raise StringprepError(ret)
|
||||
bytes_domain = ascii_domain
|
||||
free(ascii_domain)
|
||||
return bytes_domain
|
||||
|
@@ -319,6 +319,9 @@ class SlixTest(unittest.TestCase):
|
||||
plugins -- List of plugins to register. By default, all plugins
|
||||
are loaded.
|
||||
"""
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
if mode == 'client':
|
||||
self.xmpp = ClientXMPP(jid, password,
|
||||
sasl_mech=sasl_mech,
|
||||
@@ -402,8 +405,7 @@ class SlixTest(unittest.TestCase):
|
||||
parts.append('xmlns="%s"' % default_ns)
|
||||
return header % ' '.join(parts)
|
||||
|
||||
def recv(self, data, defaults=[], method='exact',
|
||||
use_values=True, timeout=1):
|
||||
def recv(self, data, defaults=None, method='exact', use_values=True, timeout=1):
|
||||
"""
|
||||
Pass data to the dummy XMPP client as if it came from an XMPP server.
|
||||
|
||||
|
2
slixmpp/thirdparty/__init__.py
vendored
2
slixmpp/thirdparty/__init__.py
vendored
@@ -3,5 +3,5 @@ try:
|
||||
except:
|
||||
from slixmpp.thirdparty.gnupg import GPG
|
||||
|
||||
from slixmpp.thirdparty import socks
|
||||
from slixmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
|
||||
from slixmpp.thirdparty.orderedset import OrderedSet
|
||||
|
89
slixmpp/thirdparty/orderedset.py
vendored
Normal file
89
slixmpp/thirdparty/orderedset.py
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) 2009 Raymond Hettinger
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import collections
|
||||
|
||||
class OrderedSet(collections.MutableSet):
|
||||
|
||||
def __init__(self, iterable=None):
|
||||
self.end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.map = {} # key --> [key, prev, next]
|
||||
if iterable is not None:
|
||||
self |= iterable
|
||||
|
||||
def __len__(self):
|
||||
return len(self.map)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.map
|
||||
|
||||
def add(self, key):
|
||||
if key not in self.map:
|
||||
end = self.end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.map[key] = [key, curr, end]
|
||||
|
||||
def discard(self, key):
|
||||
if key in self.map:
|
||||
key, prev, next = self.map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def pop(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('set is empty')
|
||||
key = self.end[1][0] if last else self.end[2][0]
|
||||
self.discard(key)
|
||||
return key
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, list(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedSet):
|
||||
return len(self) == len(other) and list(self) == list(other)
|
||||
return set(self) == set(other)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
s = OrderedSet('abracadaba')
|
||||
t = OrderedSet('simsalabim')
|
||||
print(s | t)
|
||||
print(s & t)
|
||||
print(s - t)
|
378
slixmpp/thirdparty/socks.py
vendored
378
slixmpp/thirdparty/socks.py
vendored
@@ -1,378 +0,0 @@
|
||||
"""SocksiPy - Python SOCKS module.
|
||||
Version 1.00
|
||||
|
||||
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.
|
||||
|
||||
|
||||
This module provides a standard socket-like interface for Python
|
||||
for tunneling connections through SOCKS proxies.
|
||||
|
||||
|
||||
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
|
||||
for use in PyLoris (http://pyloris.sourceforge.net/)
|
||||
|
||||
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||
mainly to merge bug fixes found in Sourceforge
|
||||
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
|
||||
PROXY_TYPE_SOCKS4 = 1
|
||||
PROXY_TYPE_SOCKS5 = 2
|
||||
PROXY_TYPE_HTTP = 3
|
||||
|
||||
_defaultproxy = None
|
||||
_orgsocket = socket.socket
|
||||
|
||||
class ProxyError(Exception): pass
|
||||
class GeneralProxyError(ProxyError): pass
|
||||
class Socks5AuthError(ProxyError): pass
|
||||
class Socks5Error(ProxyError): pass
|
||||
class Socks4Error(ProxyError): pass
|
||||
class HTTPError(ProxyError): pass
|
||||
|
||||
_generalerrors = ("success",
|
||||
"invalid data",
|
||||
"not connected",
|
||||
"not available",
|
||||
"bad proxy type",
|
||||
"bad input")
|
||||
|
||||
_socks5errors = ("succeeded",
|
||||
"general SOCKS server failure",
|
||||
"connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported",
|
||||
"Unknown error")
|
||||
|
||||
_socks5autherrors = ("succeeded",
|
||||
"authentication is required",
|
||||
"all offered authentication methods were rejected",
|
||||
"unknown username or invalid password",
|
||||
"unknown error")
|
||||
|
||||
_socks4errors = ("request granted",
|
||||
"request rejected or failed",
|
||||
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||
"request rejected because the client program and identd report different user-ids",
|
||||
"unknown error")
|
||||
|
||||
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets a default proxy which all further socksocket objects will use,
|
||||
unless explicitly changed.
|
||||
"""
|
||||
global _defaultproxy
|
||||
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def wrapmodule(module):
|
||||
"""wrapmodule(module)
|
||||
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||
a default proxy using setdefaultproxy(...) first.
|
||||
This will only work on modules that import socket directly into the namespace;
|
||||
most of the Python Standard Library falls into this category.
|
||||
"""
|
||||
if _defaultproxy != None:
|
||||
module.socket.socket = socksocket
|
||||
else:
|
||||
raise GeneralProxyError((4, "no proxy specified"))
|
||||
|
||||
class socksocket(socket.socket):
|
||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||
Open a SOCKS enabled socket. The parameters are the same as
|
||||
those of the standard socket init. In order for SOCKS to work,
|
||||
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||
"""
|
||||
|
||||
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
||||
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||
if _defaultproxy != None:
|
||||
self.__proxy = _defaultproxy
|
||||
else:
|
||||
self.__proxy = (None, None, None, None, None, None)
|
||||
self.__proxysockname = None
|
||||
self.__proxypeername = None
|
||||
|
||||
def __recvall(self, count):
|
||||
"""__recvall(count) -> data
|
||||
Receive EXACTLY the number of bytes requested from the socket.
|
||||
Blocks until the required number of bytes have been received.
|
||||
"""
|
||||
data = self.recv(count)
|
||||
while len(data) < count:
|
||||
d = self.recv(count-len(data))
|
||||
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||
data = data + d
|
||||
return data
|
||||
|
||||
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets the proxy to be used.
|
||||
proxytype - The type of the proxy to be used. Three types
|
||||
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||
addr - The address of the server (IP or DNS).
|
||||
port - The port of the server. Defaults to 1080 for SOCKS
|
||||
servers and 8080 for HTTP proxy servers.
|
||||
rdns - Should DNS queries be preformed on the remote side
|
||||
(rather than the local side). The default is True.
|
||||
Note: This has no effect with SOCKS4 servers.
|
||||
username - Username to authenticate with to the server.
|
||||
The default is no authentication.
|
||||
password - Password to authenticate with to the server.
|
||||
Only relevant when username is also provided.
|
||||
"""
|
||||
self.__proxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def __negotiatesocks5(self, destaddr, destport):
|
||||
"""__negotiatesocks5(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS5 server.
|
||||
"""
|
||||
# First we'll send the authentication packages we support.
|
||||
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
|
||||
# The username/password details were supplied to the
|
||||
# setproxy method so we support the USERNAME/PASSWORD
|
||||
# authentication (in addition to the standard none).
|
||||
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
|
||||
else:
|
||||
# No username/password were entered, therefore we
|
||||
# only support connections with no authentication.
|
||||
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
|
||||
# We'll receive the server's response to determine which
|
||||
# method was selected
|
||||
chosenauth = self.__recvall(2)
|
||||
if chosenauth[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Check the chosen authentication method
|
||||
if chosenauth[1:2] == chr(0x00).encode():
|
||||
# No authentication is required
|
||||
pass
|
||||
elif chosenauth[1:2] == chr(0x02).encode():
|
||||
# Okay, we need to perform a basic username/password
|
||||
# authentication.
|
||||
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
|
||||
authstat = self.__recvall(2)
|
||||
if authstat[0:1] != chr(0x01).encode():
|
||||
# Bad response
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if authstat[1:2] != chr(0x00).encode():
|
||||
# Authentication failed
|
||||
self.close()
|
||||
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||
# Authentication succeeded
|
||||
else:
|
||||
# Reaching here is always bad
|
||||
self.close()
|
||||
if chosenauth[1] == chr(0xFF).encode():
|
||||
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||
else:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Now we can request the actual connection
|
||||
req = struct.pack('BBB', 0x05, 0x01, 0x00)
|
||||
# If the given destination address is an IP address, we'll
|
||||
# use the IPv4 address request even if remote resolving was specified.
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
except socket.error:
|
||||
# Well it's not an IP number, so it's probably a DNS name.
|
||||
if self.__proxy[3]:
|
||||
# Resolve remotely
|
||||
ipaddr = None
|
||||
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
|
||||
else:
|
||||
# Resolve locally
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
req = req + struct.pack(">H", destport)
|
||||
self.sendall(req)
|
||||
# Get the response
|
||||
resp = self.__recvall(4)
|
||||
if resp[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
elif resp[1:2] != chr(0x00).encode():
|
||||
# Connection failed
|
||||
self.close()
|
||||
if ord(resp[1:2])<=8:
|
||||
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||
else:
|
||||
raise Socks5Error((9, _socks5errors[9]))
|
||||
# Get the bound address/port
|
||||
elif resp[3:4] == chr(0x01).encode():
|
||||
boundaddr = self.__recvall(4)
|
||||
elif resp[3:4] == chr(0x03).encode():
|
||||
resp = resp + self.recv(1)
|
||||
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||
else:
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||
self.__proxysockname = (boundaddr, boundport)
|
||||
if ipaddr != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def getproxysockname(self):
|
||||
"""getsockname() -> address info
|
||||
Returns the bound IP address and port number at the proxy.
|
||||
"""
|
||||
return self.__proxysockname
|
||||
|
||||
def getproxypeername(self):
|
||||
"""getproxypeername() -> address info
|
||||
Returns the IP and port number of the proxy.
|
||||
"""
|
||||
return _orgsocket.getpeername(self)
|
||||
|
||||
def getpeername(self):
|
||||
"""getpeername() -> address info
|
||||
Returns the IP address and port number of the destination
|
||||
machine (note: getproxypeername returns the proxy)
|
||||
"""
|
||||
return self.__proxypeername
|
||||
|
||||
def __negotiatesocks4(self,destaddr,destport):
|
||||
"""__negotiatesocks4(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS4 server.
|
||||
"""
|
||||
# Check if the destination address provided is an IP address
|
||||
rmtrslv = False
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
except socket.error:
|
||||
# It's a DNS name. Check where it should be resolved.
|
||||
if self.__proxy[3]:
|
||||
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||
rmtrslv = True
|
||||
else:
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
# Construct the request packet
|
||||
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||
# The username parameter is considered userid for SOCKS4
|
||||
if self.__proxy[4] != None:
|
||||
req = req + self.__proxy[4]
|
||||
req = req + chr(0x00).encode()
|
||||
# DNS name if remote resolving is required
|
||||
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||
# called SOCKS4A and may not be supported in all cases.
|
||||
if rmtrslv:
|
||||
req = req + destaddr + chr(0x00).encode()
|
||||
self.sendall(req)
|
||||
# Get the response from the server
|
||||
resp = self.__recvall(8)
|
||||
if resp[0:1] != chr(0x00).encode():
|
||||
# Bad data
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
if resp[1:2] != chr(0x5A).encode():
|
||||
# Server returned an error
|
||||
self.close()
|
||||
if ord(resp[1:2]) in (91, 92, 93):
|
||||
self.close()
|
||||
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||
else:
|
||||
raise Socks4Error((94, _socks4errors[4]))
|
||||
# Get the bound address/port
|
||||
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
|
||||
if rmtrslv != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def __negotiatehttp(self, destaddr, destport):
|
||||
"""__negotiatehttp(self,destaddr,destport)
|
||||
Negotiates a connection through an HTTP server.
|
||||
"""
|
||||
# If we need to resolve locally, we do this now
|
||||
if not self.__proxy[3]:
|
||||
addr = socket.gethostbyname(destaddr)
|
||||
else:
|
||||
addr = destaddr
|
||||
self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
|
||||
# We read the response until we get the string "\r\n\r\n"
|
||||
resp = self.recv(1)
|
||||
while resp.find("\r\n\r\n".encode()) == -1:
|
||||
resp = resp + self.recv(1)
|
||||
# We just need the first line to check if the connection
|
||||
# was successful
|
||||
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
try:
|
||||
statuscode = int(statusline[1])
|
||||
except ValueError:
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if statuscode != 200:
|
||||
self.close()
|
||||
raise HTTPError((statuscode, statusline[2]))
|
||||
self.__proxysockname = ("0.0.0.0", 0)
|
||||
self.__proxypeername = (addr, destport)
|
||||
|
||||
def connect(self, destpair):
|
||||
"""connect(self, despair)
|
||||
Connects to the specified destination through a proxy.
|
||||
destpar - A tuple of the IP/DNS address and the port number.
|
||||
(identical to socket's connect).
|
||||
To select the proxy server use setproxy().
|
||||
"""
|
||||
# Do a minimal input check first
|
||||
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
|
||||
raise GeneralProxyError((5, _generalerrors[5]))
|
||||
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatehttp(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == None:
|
||||
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||
else:
|
||||
raise GeneralProxyError((4, _generalerrors[4]))
|
@@ -9,5 +9,5 @@
|
||||
# We don't want to have to import the entire library
|
||||
# just to get the version info for setup.py
|
||||
|
||||
__version__ = '1.0'
|
||||
__version_info__ = (1, 0)
|
||||
__version__ = '1.1'
|
||||
__version_info__ = (1, 1)
|
||||
|
@@ -181,4 +181,4 @@ def verify(expected, raw_cert):
|
||||
return True
|
||||
|
||||
raise CertificateError(
|
||||
'Could not match certficate against hostname: %s' % expected)
|
||||
'Could not match certificate against hostname: %s' % expected)
|
||||
|
@@ -193,7 +193,7 @@ def get_A(host, resolver=None, use_aiodns=True, loop=None):
|
||||
except Exception as e:
|
||||
log.debug('DNS: Exception while querying for %s A records: %s', host, e)
|
||||
recs = []
|
||||
return recs
|
||||
return [rec.host for rec in recs]
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
@@ -240,7 +240,7 @@ def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
|
||||
except Exception as e:
|
||||
log.debug('DNS: Exception while querying for %s AAAA records: %s', host, e)
|
||||
recs = []
|
||||
return recs
|
||||
return [rec.host for rec in recs]
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
|
||||
|
@@ -558,10 +558,13 @@ class ElementBase(object):
|
||||
|
||||
.. versionadded:: 1.0-Beta1
|
||||
"""
|
||||
values = {}
|
||||
values = OrderedDict()
|
||||
values['lang'] = self['lang']
|
||||
for interface in self.interfaces:
|
||||
values[interface] = self[interface]
|
||||
if isinstance(self[interface], JID):
|
||||
values[interface] = self[interface].jid
|
||||
else:
|
||||
values[interface] = self[interface]
|
||||
if interface in self.lang_interfaces:
|
||||
values['%s|*' % interface] = self['%s|*' % interface]
|
||||
for plugin, stanza in self.plugins.items():
|
||||
@@ -672,6 +675,8 @@ class ElementBase(object):
|
||||
if lang and attrib in self.lang_interfaces:
|
||||
kwargs['lang'] = lang
|
||||
|
||||
kwargs = OrderedDict(kwargs)
|
||||
|
||||
if attrib == 'substanzas':
|
||||
return self.iterables
|
||||
elif attrib in self.interfaces or attrib == 'lang':
|
||||
@@ -748,6 +753,8 @@ class ElementBase(object):
|
||||
if lang and attrib in self.lang_interfaces:
|
||||
kwargs['lang'] = lang
|
||||
|
||||
kwargs = OrderedDict(kwargs)
|
||||
|
||||
if attrib in self.interfaces or attrib == 'lang':
|
||||
if value is not None:
|
||||
set_method = "set_%s" % attrib.lower()
|
||||
@@ -834,6 +841,8 @@ class ElementBase(object):
|
||||
if lang and attrib in self.lang_interfaces:
|
||||
kwargs['lang'] = lang
|
||||
|
||||
kwargs = OrderedDict(kwargs)
|
||||
|
||||
if attrib in self.interfaces or attrib == 'lang':
|
||||
del_method = "del_%s" % attrib.lower()
|
||||
del_method2 = "del%s" % attrib.title()
|
||||
|
@@ -174,7 +174,7 @@ def _get_highlight():
|
||||
def __init__(self, string):
|
||||
self.string = string
|
||||
def __str__(self):
|
||||
return highlight(str(self.string).strip(), LEXER, FORMATTER)
|
||||
return highlight(str(self.string), LEXER, FORMATTER).strip()
|
||||
|
||||
return Highlighter
|
||||
except ImportError:
|
||||
|
@@ -287,21 +287,15 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
def _connect_routine(self):
|
||||
self.event_when_connected = "connected"
|
||||
|
||||
try:
|
||||
record = yield from self.pick_dns_answer(self.default_domain)
|
||||
except StopIteration:
|
||||
# No more DNS records to try
|
||||
self.dns_answers = None
|
||||
return
|
||||
record = yield from self.pick_dns_answer(self.default_domain)
|
||||
if record is not None:
|
||||
host, address, port = record
|
||||
self.address = (address, port)
|
||||
self._service_name = host
|
||||
else:
|
||||
if record:
|
||||
host, address, port = record
|
||||
self.address = (address, port)
|
||||
self._service_name = host
|
||||
else:
|
||||
# No DNS records left, stop iterating
|
||||
# and try (host, port) as a last resort
|
||||
self.dns_answers = None
|
||||
# No DNS records left, stop iterating
|
||||
# and try (host, port) as a last resort
|
||||
self.dns_answers = None
|
||||
|
||||
yield from asyncio.sleep(self.connect_loop_wait)
|
||||
try:
|
||||
@@ -354,6 +348,7 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self.socket = self.transport.get_extra_info("socket")
|
||||
self.init_parser()
|
||||
self.send_raw(self.stream_header)
|
||||
self.dns_answers = None
|
||||
|
||||
def data_received(self, data):
|
||||
"""Called when incoming data is received on the socket.
|
||||
@@ -497,14 +492,14 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
|
||||
ssl_connect_routine = self.loop.create_connection(lambda: self, ssl=self.ssl_context,
|
||||
sock=self.socket,
|
||||
server_hostname=self.address[0])
|
||||
server_hostname=self.default_domain)
|
||||
@asyncio.coroutine
|
||||
def ssl_coro():
|
||||
try:
|
||||
transp, prot = yield from ssl_connect_routine
|
||||
except ssl.SSLError as e:
|
||||
log.error('CERT: Invalid certificate trust chain.')
|
||||
log.debug('SSL: Unable to connect', exc_info=True)
|
||||
log.error('CERT: Invalid certificate trust chain.')
|
||||
if not self.event_handled('ssl_invalid_chain'):
|
||||
self.disconnect()
|
||||
else:
|
||||
@@ -513,7 +508,6 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
der_cert = transp.get_extra_info("socket").getpeercert(True)
|
||||
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
||||
self.event('ssl_cert', pem_cert)
|
||||
self.socket = self.transport.get_extra_info("socket")
|
||||
|
||||
asyncio.async(ssl_coro())
|
||||
|
||||
@@ -663,7 +657,10 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
dns_records = yield from self.get_dns_records(domain, port)
|
||||
self.dns_answers = iter(dns_records)
|
||||
|
||||
return next(self.dns_answers)
|
||||
try:
|
||||
return next(self.dns_answers)
|
||||
except StopIteration:
|
||||
return
|
||||
|
||||
def add_event_handler(self, name, pointer, disposable=False):
|
||||
"""Add a custom event handler that will be executed whenever
|
||||
|
@@ -385,7 +385,7 @@ class TestElementBase(SlixTest):
|
||||
interfaces = set(('bar', 'baz'))
|
||||
|
||||
def setBar(self, value):
|
||||
self._set_sub_text("path/to/only/bar", value);
|
||||
self._set_sub_text("path/to/only/bar", value)
|
||||
|
||||
def getBar(self):
|
||||
return self._get_sub_text("path/to/only/bar")
|
||||
@@ -394,7 +394,7 @@ class TestElementBase(SlixTest):
|
||||
self._del_sub("path/to/only/bar")
|
||||
|
||||
def setBaz(self, value):
|
||||
self._set_sub_text("path/to/just/baz", value);
|
||||
self._set_sub_text("path/to/just/baz", value)
|
||||
|
||||
def getBaz(self):
|
||||
return self._get_sub_text("path/to/just/baz")
|
||||
|
@@ -11,8 +11,8 @@ class TestDataForms(SlixTest):
|
||||
|
||||
def setUp(self):
|
||||
register_stanza_plugin(Message, xep_0004.Form)
|
||||
register_stanza_plugin(xep_0004.Form, xep_0004.FormField)
|
||||
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption)
|
||||
register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
|
||||
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
|
||||
|
||||
def testMultipleInstructions(self):
|
||||
"""Testing using multiple instructions elements in a data form."""
|
||||
@@ -68,7 +68,7 @@ class TestDataForms(SlixTest):
|
||||
'value': 'cool'},
|
||||
{'label': 'Urgh!',
|
||||
'value': 'urgh'}]}
|
||||
form['fields'] = fields
|
||||
form.set_fields(fields)
|
||||
|
||||
|
||||
self.check(msg, """
|
||||
@@ -141,13 +141,13 @@ class TestDataForms(SlixTest):
|
||||
'value': 'cool'},
|
||||
{'label': 'Urgh!',
|
||||
'value': 'urgh'}]}
|
||||
form['fields'] = fields
|
||||
form.set_fields(fields)
|
||||
|
||||
form['type'] = 'submit'
|
||||
form['values'] = {'f1': 'username',
|
||||
form.set_values({'f1': 'username',
|
||||
'f2': 'hunter2',
|
||||
'f3': 'A long\nmultiline\nmessage',
|
||||
'f4': 'cool'}
|
||||
'f4': 'cool'})
|
||||
|
||||
self.check(form, """
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
@@ -189,7 +189,7 @@ class TestDataForms(SlixTest):
|
||||
'value': 'cool'},
|
||||
{'label': 'Urgh!',
|
||||
'value': 'urgh'}]}
|
||||
form['fields'] = fields
|
||||
form.set_fields(fields)
|
||||
|
||||
form['type'] = 'cancel'
|
||||
|
||||
@@ -197,5 +197,52 @@ class TestDataForms(SlixTest):
|
||||
<x xmlns="jabber:x:data" type="cancel" />
|
||||
""")
|
||||
|
||||
def testReported(self):
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
form['type'] = 'result'
|
||||
|
||||
form.add_reported(var='f1', ftype='text-single', label='Username')
|
||||
|
||||
form.add_item({'f1': 'username@example.org'})
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="result">
|
||||
<reported>
|
||||
<field var="f1" type="text-single" label="Username" />
|
||||
</reported>
|
||||
<item>
|
||||
<field var="f1">
|
||||
<value>username@example.org</value>
|
||||
</field>
|
||||
</item>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
def testSetReported(self):
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
form['type'] = 'result'
|
||||
|
||||
reported = {'f1': {
|
||||
'var': 'f1',
|
||||
'type': 'text-single',
|
||||
'label': 'Username'
|
||||
}}
|
||||
|
||||
form.set_reported(reported)
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="result">
|
||||
<reported>
|
||||
<field var="f1" type="text-single" label="Username" />
|
||||
</reported>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)
|
||||
|
189
tests/test_stanza_xep_0122.py
Normal file
189
tests/test_stanza_xep_0122.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import unittest
|
||||
|
||||
from slixmpp import Message
|
||||
from slixmpp.test import SlixTest
|
||||
import slixmpp.plugins.xep_0004 as xep_0004
|
||||
import slixmpp.plugins.xep_0122 as xep_0122
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
|
||||
|
||||
class TestDataForms(SlixTest):
|
||||
|
||||
def setUp(self):
|
||||
register_stanza_plugin(Message, xep_0004.Form)
|
||||
register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
|
||||
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
|
||||
register_stanza_plugin(xep_0004.FormField, xep_0122.FormValidation)
|
||||
|
||||
def test_basic_validation(self):
|
||||
"""Testing basic validation setting and getting."""
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
field = form.add_field(var='f1',
|
||||
ftype='text-single',
|
||||
label='Text',
|
||||
desc='A text field',
|
||||
required=True,
|
||||
value='Some text!')
|
||||
|
||||
validation = field['validate']
|
||||
validation['datatype'] = 'xs:string'
|
||||
validation.set_basic(True)
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="f1" type="text-single" label="Text">
|
||||
<desc>A text field</desc>
|
||||
<required />
|
||||
<value>Some text!</value>
|
||||
<validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:string">
|
||||
<basic/>
|
||||
</validate>
|
||||
</field>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.assertTrue(validation.get_basic())
|
||||
self.assertFalse(validation.get_open())
|
||||
self.assertFalse(validation.get_range())
|
||||
self.assertFalse(validation.get_regex())
|
||||
|
||||
def test_open_validation(self):
|
||||
"""Testing open validation setting and getting."""
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
field = form.add_field(var='f1',
|
||||
ftype='text-single',
|
||||
label='Text',
|
||||
desc='A text field',
|
||||
required=True,
|
||||
value='Some text!')
|
||||
|
||||
validation = field['validate']
|
||||
validation.set_open(True)
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="f1" type="text-single" label="Text">
|
||||
<desc>A text field</desc>
|
||||
<required />
|
||||
<value>Some text!</value>
|
||||
<validate xmlns="http://jabber.org/protocol/xdata-validate">
|
||||
<open />
|
||||
</validate>
|
||||
</field>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.assertFalse(validation.get_basic())
|
||||
self.assertTrue(validation.get_open())
|
||||
self.assertFalse(validation.get_range())
|
||||
self.assertFalse(validation.get_regex())
|
||||
|
||||
def test_regex_validation(self):
|
||||
"""Testing regex validation setting and getting."""
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
field = form.add_field(var='f1',
|
||||
ftype='text-single',
|
||||
label='Text',
|
||||
desc='A text field',
|
||||
required=True,
|
||||
value='Some text!')
|
||||
|
||||
regex_value = '[0-9]+'
|
||||
|
||||
validation = field['validate']
|
||||
validation.set_regex(regex_value)
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="f1" type="text-single" label="Text">
|
||||
<desc>A text field</desc>
|
||||
<required />
|
||||
<value>Some text!</value>
|
||||
<validate xmlns="http://jabber.org/protocol/xdata-validate">
|
||||
<regex>[0-9]+</regex>
|
||||
</validate>
|
||||
</field>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.assertFalse(validation.get_basic())
|
||||
self.assertFalse(validation.get_open())
|
||||
self.assertFalse(validation.get_range())
|
||||
self.assertTrue(validation.get_regex())
|
||||
|
||||
self.assertEqual(regex_value, validation.get_regex())
|
||||
|
||||
def test_range_validation(self):
|
||||
"""Testing range validation setting and getting."""
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
field = form.add_field(var='f1',
|
||||
ftype='text-single',
|
||||
label='Text',
|
||||
desc='A text field',
|
||||
required=True,
|
||||
value='Some text!')
|
||||
|
||||
validation = field['validate']
|
||||
validation.set_range(True, minimum=0, maximum=10)
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="f1" type="text-single" label="Text">
|
||||
<desc>A text field</desc>
|
||||
<required />
|
||||
<value>Some text!</value>
|
||||
<validate xmlns="http://jabber.org/protocol/xdata-validate">
|
||||
<range min="0" max="10" />
|
||||
</validate>
|
||||
</field>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.assertDictEqual(dict(minimum=str(0), maximum=str(10)), validation.get_range())
|
||||
|
||||
def test_reported_field_validation(self):
|
||||
"""
|
||||
Testing adding validation to the field when it's stored in the reported.
|
||||
:return:
|
||||
"""
|
||||
msg = self.Message()
|
||||
form = msg['form']
|
||||
field = form.add_reported(var='f1', ftype='text-single', label='Text')
|
||||
validation = field['validate']
|
||||
validation.set_basic(True)
|
||||
|
||||
form.add_item({'f1': 'Some text!'})
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<reported>
|
||||
<field var="f1" type="text-single" label="Text">
|
||||
<validate xmlns="http://jabber.org/protocol/xdata-validate">
|
||||
<basic />
|
||||
</validate>
|
||||
</field>
|
||||
</reported>
|
||||
<item>
|
||||
<field var="f1">
|
||||
<value>Some text!</value>
|
||||
</field>
|
||||
</item>
|
||||
</x>
|
||||
</message>
|
||||
""")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)
|
@@ -7,7 +7,6 @@ namespace='sn'
|
||||
|
||||
class TestSensorDataStanzas(SlixTest):
|
||||
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
#register_stanza_plugin(Iq, xep_0323.stanza.Request)
|
||||
@@ -59,8 +58,8 @@ class TestSensorDataStanzas(SlixTest):
|
||||
iq['req']['momentary'] = 'true'
|
||||
|
||||
|
||||
iq['req'].add_node("Device02", "Source02", "CacheType");
|
||||
iq['req'].add_node("Device44");
|
||||
iq['req'].add_node("Device02", "Source02", "CacheType")
|
||||
iq['req'].add_node("Device44")
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='get'
|
||||
@@ -75,7 +74,7 @@ class TestSensorDataStanzas(SlixTest):
|
||||
"""
|
||||
)
|
||||
|
||||
iq['req'].del_node("Device02");
|
||||
iq['req'].del_node("Device02")
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='get'
|
||||
@@ -89,7 +88,7 @@ class TestSensorDataStanzas(SlixTest):
|
||||
"""
|
||||
)
|
||||
|
||||
iq['req'].del_nodes();
|
||||
iq['req'].del_nodes()
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='get'
|
||||
@@ -115,8 +114,8 @@ class TestSensorDataStanzas(SlixTest):
|
||||
iq['req']['momentary'] = 'true'
|
||||
|
||||
|
||||
iq['req'].add_field("Top temperature");
|
||||
iq['req'].add_field("Bottom temperature");
|
||||
iq['req'].add_field("Top temperature")
|
||||
iq['req'].add_field("Bottom temperature")
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='get'
|
||||
@@ -237,12 +236,12 @@ class TestSensorDataStanzas(SlixTest):
|
||||
msg['to'] = 'master@clayster.com/amr'
|
||||
msg['fields']['seqnr'] = '1'
|
||||
|
||||
node = msg['fields'].add_node("Device02");
|
||||
ts = node.add_timestamp("2013-03-07T16:24:30");
|
||||
node = msg['fields'].add_node("Device02")
|
||||
ts = node.add_timestamp("2013-03-07T16:24:30")
|
||||
|
||||
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K');
|
||||
data['momentary'] = 'true';
|
||||
data['automaticReadout'] = 'true';
|
||||
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K')
|
||||
data['momentary'] = 'true'
|
||||
data['automaticReadout'] = 'true'
|
||||
|
||||
self.check(msg,"""
|
||||
<message from='device@clayster.com'
|
||||
@@ -258,10 +257,9 @@ class TestSensorDataStanzas(SlixTest):
|
||||
"""
|
||||
)
|
||||
|
||||
node = msg['fields'].add_node("EmptyDevice");
|
||||
node = msg['fields'].add_node("Device04");
|
||||
ts = node.add_timestamp("EmptyTimestamp");
|
||||
|
||||
node = msg['fields'].add_node("EmptyDevice")
|
||||
node = msg['fields'].add_node("Device04")
|
||||
ts = node.add_timestamp("EmptyTimestamp")
|
||||
|
||||
self.check(msg,"""
|
||||
<message from='device@clayster.com'
|
||||
@@ -281,32 +279,32 @@ class TestSensorDataStanzas(SlixTest):
|
||||
"""
|
||||
)
|
||||
|
||||
node = msg['fields'].add_node("Device77");
|
||||
ts = node.add_timestamp("2013-05-03T12:00:01");
|
||||
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K');
|
||||
data['historicalDay'] = 'true';
|
||||
data = ts.add_data(typename="numeric", name="Speed", value="312.42", unit='km/h');
|
||||
data['historicalWeek'] = 'false';
|
||||
data = ts.add_data(typename="string", name="Temperature name", value="Bottom oil");
|
||||
data['historicalMonth'] = 'true';
|
||||
data = ts.add_data(typename="string", name="Speed name", value="Top speed");
|
||||
data['historicalQuarter'] = 'false';
|
||||
data = ts.add_data(typename="dateTime", name="T1", value="1979-01-01T00:00:00");
|
||||
data['historicalYear'] = 'true';
|
||||
data = ts.add_data(typename="dateTime", name="T2", value="2000-01-01T01:02:03");
|
||||
data['historicalOther'] = 'false';
|
||||
data = ts.add_data(typename="timeSpan", name="TS1", value="P5Y");
|
||||
data['missing'] = 'true';
|
||||
data = ts.add_data(typename="timeSpan", name="TS2", value="PT2M1S");
|
||||
data['manualEstimate'] = 'false';
|
||||
data = ts.add_data(typename="enum", name="top color", value="red", dataType="string");
|
||||
data['invoiced'] = 'true';
|
||||
data = ts.add_data(typename="enum", name="bottom color", value="black", dataType="string");
|
||||
data['powerFailure'] = 'false';
|
||||
data = ts.add_data(typename="boolean", name="Temperature real", value="false");
|
||||
data['historicalDay'] = 'true';
|
||||
data = ts.add_data(typename="boolean", name="Speed real", value="true");
|
||||
data['historicalWeek'] = 'false';
|
||||
node = msg['fields'].add_node("Device77")
|
||||
ts = node.add_timestamp("2013-05-03T12:00:01")
|
||||
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K')
|
||||
data['historicalDay'] = 'true'
|
||||
data = ts.add_data(typename="numeric", name="Speed", value="312.42", unit='km/h')
|
||||
data['historicalWeek'] = 'false'
|
||||
data = ts.add_data(typename="string", name="Temperature name", value="Bottom oil")
|
||||
data['historicalMonth'] = 'true'
|
||||
data = ts.add_data(typename="string", name="Speed name", value="Top speed")
|
||||
data['historicalQuarter'] = 'false'
|
||||
data = ts.add_data(typename="dateTime", name="T1", value="1979-01-01T00:00:00")
|
||||
data['historicalYear'] = 'true'
|
||||
data = ts.add_data(typename="dateTime", name="T2", value="2000-01-01T01:02:03")
|
||||
data['historicalOther'] = 'false'
|
||||
data = ts.add_data(typename="timeSpan", name="TS1", value="P5Y")
|
||||
data['missing'] = 'true'
|
||||
data = ts.add_data(typename="timeSpan", name="TS2", value="PT2M1S")
|
||||
data['manualEstimate'] = 'false'
|
||||
data = ts.add_data(typename="enum", name="top color", value="red", dataType="string")
|
||||
data['invoiced'] = 'true'
|
||||
data = ts.add_data(typename="enum", name="bottom color", value="black", dataType="string")
|
||||
data['powerFailure'] = 'false'
|
||||
data = ts.add_data(typename="boolean", name="Temperature real", value="false")
|
||||
data['historicalDay'] = 'true'
|
||||
data = ts.add_data(typename="boolean", name="Speed real", value="true")
|
||||
data['historicalWeek'] = 'false'
|
||||
|
||||
self.check(msg,"""
|
||||
<message from='device@clayster.com'
|
||||
@@ -344,19 +342,17 @@ class TestSensorDataStanzas(SlixTest):
|
||||
|
||||
|
||||
def testTimestamp(self):
|
||||
msg = self.Message();
|
||||
msg = self.Message()
|
||||
|
||||
msg['from'] = 'device@clayster.com'
|
||||
msg['to'] = 'master@clayster.com/amr'
|
||||
msg['fields']['seqnr'] = '1'
|
||||
|
||||
node = msg['fields'].add_node("Device02");
|
||||
node = msg['fields'].add_node("Device03");
|
||||
|
||||
ts = node.add_timestamp("2013-03-07T16:24:30");
|
||||
ts = node.add_timestamp("2013-03-07T16:24:31");
|
||||
|
||||
node = msg['fields'].add_node("Device02")
|
||||
node = msg['fields'].add_node("Device03")
|
||||
|
||||
ts = node.add_timestamp("2013-03-07T16:24:30")
|
||||
ts = node.add_timestamp("2013-03-07T16:24:31")
|
||||
|
||||
self.check(msg,"""
|
||||
<message from='device@clayster.com'
|
||||
|
@@ -16,7 +16,6 @@ namespace='sn'
|
||||
|
||||
class TestControlStanzas(SlixTest):
|
||||
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
@@ -29,8 +28,8 @@ class TestControlStanzas(SlixTest):
|
||||
iq['from'] = 'master@clayster.com/amr'
|
||||
iq['to'] = 'device@clayster.com'
|
||||
iq['id'] = '1'
|
||||
iq['set'].add_node("Device02", "Source02", "MyCacheType");
|
||||
iq['set'].add_node("Device15");
|
||||
iq['set'].add_node("Device02", "Source02", "MyCacheType")
|
||||
iq['set'].add_node("Device15")
|
||||
iq['set'].add_data("Tjohej", "boolean", "true")
|
||||
|
||||
self.check(iq,"""
|
||||
@@ -47,7 +46,7 @@ class TestControlStanzas(SlixTest):
|
||||
"""
|
||||
)
|
||||
|
||||
iq['set'].del_node("Device02");
|
||||
iq['set'].del_node("Device02")
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='set'
|
||||
@@ -62,7 +61,7 @@ class TestControlStanzas(SlixTest):
|
||||
"""
|
||||
)
|
||||
|
||||
iq['set'].del_nodes();
|
||||
iq['set'].del_nodes()
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='set'
|
||||
@@ -84,8 +83,8 @@ class TestControlStanzas(SlixTest):
|
||||
msg = self.Message()
|
||||
msg['from'] = 'master@clayster.com/amr'
|
||||
msg['to'] = 'device@clayster.com'
|
||||
msg['set'].add_node("Device02");
|
||||
msg['set'].add_node("Device15");
|
||||
msg['set'].add_node("Device02")
|
||||
msg['set'].add_node("Device15")
|
||||
msg['set'].add_data("Tjohej", "boolean", "true")
|
||||
|
||||
self.check(msg,"""
|
||||
@@ -111,7 +110,7 @@ class TestControlStanzas(SlixTest):
|
||||
iq['from'] = 'master@clayster.com/amr'
|
||||
iq['to'] = 'device@clayster.com'
|
||||
iq['id'] = '8'
|
||||
iq['setResponse']['responseCode'] = "OK";
|
||||
iq['setResponse']['responseCode'] = "OK"
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='result'
|
||||
@@ -128,10 +127,9 @@ class TestControlStanzas(SlixTest):
|
||||
iq['from'] = 'master@clayster.com/amr'
|
||||
iq['to'] = 'device@clayster.com'
|
||||
iq['id'] = '9'
|
||||
iq['setResponse']['responseCode'] = "OtherError";
|
||||
iq['setResponse']['error']['var'] = "Output";
|
||||
iq['setResponse']['error']['text'] = "Test of other error.!";
|
||||
|
||||
iq['setResponse']['responseCode'] = "OtherError"
|
||||
iq['setResponse']['error']['var'] = "Output"
|
||||
iq['setResponse']['error']['text'] = "Test of other error.!"
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='error'
|
||||
@@ -150,11 +148,10 @@ class TestControlStanzas(SlixTest):
|
||||
iq['from'] = 'master@clayster.com/amr'
|
||||
iq['to'] = 'device@clayster.com'
|
||||
iq['id'] = '9'
|
||||
iq['setResponse']['responseCode'] = "NotFound";
|
||||
iq['setResponse'].add_node("Device17", "Source09");
|
||||
iq['setResponse'].add_node("Device18", "Source09");
|
||||
iq['setResponse'].add_data("Tjohopp");
|
||||
|
||||
iq['setResponse']['responseCode'] = "NotFound"
|
||||
iq['setResponse'].add_node("Device17", "Source09")
|
||||
iq['setResponse'].add_node("Device18", "Source09")
|
||||
iq['setResponse'].add_data("Tjohopp")
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='error'
|
||||
@@ -179,38 +176,38 @@ class TestControlStanzas(SlixTest):
|
||||
iq['from'] = 'master@clayster.com/amr'
|
||||
iq['to'] = 'device@clayster.com'
|
||||
iq['id'] = '1'
|
||||
iq['set'].add_node("Device02", "Source02", "MyCacheType");
|
||||
iq['set'].add_node("Device15");
|
||||
iq['set'].add_node("Device02", "Source02", "MyCacheType")
|
||||
iq['set'].add_node("Device15")
|
||||
|
||||
iq['set'].add_data("Tjohej", "boolean", "true");
|
||||
iq['set'].add_data("Tjohej2", "boolean", "false");
|
||||
iq['set'].add_data("Tjohej", "boolean", "true")
|
||||
iq['set'].add_data("Tjohej2", "boolean", "false")
|
||||
|
||||
iq['set'].add_data("TjohejC", "color", "FF00FF");
|
||||
iq['set'].add_data("TjohejC2", "color", "00FF00");
|
||||
iq['set'].add_data("TjohejC", "color", "FF00FF")
|
||||
iq['set'].add_data("TjohejC2", "color", "00FF00")
|
||||
|
||||
iq['set'].add_data("TjohejS", "string", "String1");
|
||||
iq['set'].add_data("TjohejS2", "string", "String2");
|
||||
iq['set'].add_data("TjohejS", "string", "String1")
|
||||
iq['set'].add_data("TjohejS2", "string", "String2")
|
||||
|
||||
iq['set'].add_data("TjohejDate", "date", "2012-01-01");
|
||||
iq['set'].add_data("TjohejDate2", "date", "1900-12-03");
|
||||
iq['set'].add_data("TjohejDate", "date", "2012-01-01")
|
||||
iq['set'].add_data("TjohejDate2", "date", "1900-12-03")
|
||||
|
||||
iq['set'].add_data("TjohejDateT4", "dateTime", "1900-12-03 12:30");
|
||||
iq['set'].add_data("TjohejDateT2", "dateTime", "1900-12-03 11:22");
|
||||
iq['set'].add_data("TjohejDateT4", "dateTime", "1900-12-03 12:30")
|
||||
iq['set'].add_data("TjohejDateT2", "dateTime", "1900-12-03 11:22")
|
||||
|
||||
iq['set'].add_data("TjohejDouble2", "double", "200.22");
|
||||
iq['set'].add_data("TjohejDouble3", "double", "-12232131.3333");
|
||||
iq['set'].add_data("TjohejDouble2", "double", "200.22")
|
||||
iq['set'].add_data("TjohejDouble3", "double", "-12232131.3333")
|
||||
|
||||
iq['set'].add_data("TjohejDur", "duration", "P5Y");
|
||||
iq['set'].add_data("TjohejDur2", "duration", "PT2M1S");
|
||||
iq['set'].add_data("TjohejDur", "duration", "P5Y")
|
||||
iq['set'].add_data("TjohejDur2", "duration", "PT2M1S")
|
||||
|
||||
iq['set'].add_data("TjohejInt", "int", "1");
|
||||
iq['set'].add_data("TjohejInt2", "int", "-42");
|
||||
iq['set'].add_data("TjohejInt", "int", "1")
|
||||
iq['set'].add_data("TjohejInt2", "int", "-42")
|
||||
|
||||
iq['set'].add_data("TjohejLong", "long", "123456789098");
|
||||
iq['set'].add_data("TjohejLong2", "long", "-90983243827489374");
|
||||
iq['set'].add_data("TjohejLong", "long", "123456789098")
|
||||
iq['set'].add_data("TjohejLong2", "long", "-90983243827489374")
|
||||
|
||||
iq['set'].add_data("TjohejTime", "time", "23:59");
|
||||
iq['set'].add_data("TjohejTime2", "time", "12:00");
|
||||
iq['set'].add_data("TjohejTime", "time", "23:59")
|
||||
iq['set'].add_data("TjohejTime2", "time", "12:00")
|
||||
|
||||
self.check(iq,"""
|
||||
<iq type='set'
|
||||
|
@@ -119,7 +119,8 @@ class TestAdHocCommands(SlixTest):
|
||||
def handle_command(iq, session):
|
||||
|
||||
def handle_form(form, session):
|
||||
results.append(form['values']['foo'])
|
||||
results.append(form.get_values()['foo'])
|
||||
session['payload'] = None
|
||||
|
||||
form = self.xmpp['xep_0004'].makeForm('form')
|
||||
form.addField(var='foo', ftype='text-single', label='Foo')
|
||||
@@ -191,10 +192,11 @@ class TestAdHocCommands(SlixTest):
|
||||
def handle_command(iq, session):
|
||||
|
||||
def handle_step2(form, session):
|
||||
results.append(form['values']['bar'])
|
||||
results.append(form.get_values()['bar'])
|
||||
session['payload'] = None
|
||||
|
||||
def handle_step1(form, session):
|
||||
results.append(form['values']['foo'])
|
||||
results.append(form.get_values()['foo'])
|
||||
|
||||
form = self.xmpp['xep_0004'].makeForm('form')
|
||||
form.addField(var='bar', ftype='text-single', label='Bar')
|
||||
@@ -426,7 +428,8 @@ class TestAdHocCommands(SlixTest):
|
||||
|
||||
def handle_form(forms, session):
|
||||
for form in forms:
|
||||
results.append(form['values']['FORM_TYPE'])
|
||||
results.append(form.get_values()['FORM_TYPE'])
|
||||
session['payload'] = None
|
||||
|
||||
form1 = self.xmpp['xep_0004'].makeForm('form')
|
||||
form1.addField(var='FORM_TYPE', ftype='hidden', value='form_1')
|
||||
|
@@ -19,7 +19,7 @@ class TestStreamSensorData(SlixTest):
|
||||
pass
|
||||
|
||||
def _time_now(self):
|
||||
return datetime.datetime.now().replace(microsecond=0).isoformat();
|
||||
return datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||
|
||||
def tearDown(self):
|
||||
self.stream_close()
|
||||
@@ -29,12 +29,12 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
|
||||
myDevice = Device("Device22")
|
||||
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"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
self.recv("""
|
||||
<iq type='get'
|
||||
@@ -73,7 +73,7 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
self.xmpp['xep_0323']._set_authenticated("darth@deathstar.com");
|
||||
self.xmpp['xep_0323']._set_authenticated("darth@deathstar.com")
|
||||
|
||||
self.recv("""
|
||||
<iq type='get'
|
||||
@@ -101,8 +101,8 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
myDevice = Device("Device44")
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -157,11 +157,11 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice = Device("Device44")
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -236,15 +236,15 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field(name='Current', typename="numeric", unit="A");
|
||||
myDevice._add_field(name='Height', typename="string");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02");
|
||||
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"});
|
||||
myDevice = Device("Device44")
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field(name='Current', typename="numeric", unit="A")
|
||||
myDevice._add_field(name='Height', typename="string")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02")
|
||||
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -308,15 +308,15 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field(name='Current', typename="numeric", unit="A");
|
||||
myDevice._add_field(name='Height', typename="string");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02");
|
||||
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"});
|
||||
myDevice = Device("Device44")
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field(name='Current', typename="numeric", unit="A")
|
||||
myDevice._add_field(name='Height', typename="string")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02")
|
||||
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -379,7 +379,7 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", callback=None);
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", callback=None)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -390,7 +390,7 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=None);
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=None)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -404,7 +404,7 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", fields=['Temperature', 'Voltage'], callback=None);
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", fields=['Temperature', 'Voltage'], callback=None)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -424,13 +424,13 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
results = [];
|
||||
results = []
|
||||
|
||||
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
|
||||
if (result == "rejected") and (error_msg == "Invalid device Device22"):
|
||||
results.append("rejected");
|
||||
results.append("rejected")
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback);
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -456,7 +456,7 @@ class TestStreamSensorData(SlixTest):
|
||||
""")
|
||||
|
||||
self.failUnless(results == ["rejected"],
|
||||
"Rejected callback was not properly executed");
|
||||
"Rejected callback was not properly executed")
|
||||
|
||||
def testRequestAcceptedAPI(self):
|
||||
|
||||
@@ -464,12 +464,12 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
results = [];
|
||||
results = []
|
||||
|
||||
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback);
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -493,7 +493,7 @@ class TestStreamSensorData(SlixTest):
|
||||
""")
|
||||
|
||||
self.failUnless(results == ["accepted"],
|
||||
"Accepted callback was not properly executed");
|
||||
"Accepted callback was not properly executed")
|
||||
|
||||
def testRequestFieldsAPI(self):
|
||||
|
||||
@@ -501,17 +501,17 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
results = [];
|
||||
callback_data = {};
|
||||
results = []
|
||||
callback_data = {}
|
||||
|
||||
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
if result == "fields":
|
||||
callback_data["nodeId"] = nodeId;
|
||||
callback_data["timestamp"] = timestamp;
|
||||
callback_data["error_msg"] = error_msg;
|
||||
callback_data["nodeId"] = nodeId
|
||||
callback_data["timestamp"] = timestamp
|
||||
callback_data["error_msg"] = error_msg
|
||||
for f in fields:
|
||||
callback_data["field_" + f['name']] = f;
|
||||
callback_data["field_" + f['name']] = f
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost",
|
||||
to_jid="you@google.com",
|
||||
@@ -560,24 +560,24 @@ class TestStreamSensorData(SlixTest):
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.failUnlessEqual(results, ["accepted","fields","done"]);
|
||||
self.failUnlessEqual(results, ["accepted","fields","done"])
|
||||
# self.assertIn("nodeId", callback_data);
|
||||
self.assertTrue("nodeId" in callback_data)
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33");
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33")
|
||||
# self.assertIn("timestamp", callback_data);
|
||||
self.assertTrue("timestamp" in callback_data);
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02");
|
||||
self.assertTrue("timestamp" in callback_data)
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
|
||||
#self.assertIn("field_Voltage", callback_data);
|
||||
self.assertTrue("field_Voltage" in callback_data);
|
||||
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}});
|
||||
self.assertTrue("field_Voltage" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
|
||||
#self.assertIn("field_TestBool", callback_data);
|
||||
self.assertTrue("field_TestBool" in callback_data);
|
||||
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" });
|
||||
self.assertTrue("field_TestBool" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
|
||||
|
||||
def testServiceDiscoveryClient(self):
|
||||
self.stream_start(mode='client',
|
||||
plugins=['xep_0030',
|
||||
'xep_0323']);
|
||||
'xep_0323'])
|
||||
|
||||
self.recv("""
|
||||
<iq type='get'
|
||||
@@ -602,7 +602,7 @@ class TestStreamSensorData(SlixTest):
|
||||
def testServiceDiscoveryComponent(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0323']);
|
||||
'xep_0323'])
|
||||
|
||||
self.recv("""
|
||||
<iq type='get'
|
||||
@@ -631,21 +631,20 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
results = [];
|
||||
callback_data = {};
|
||||
results = []
|
||||
callback_data = {}
|
||||
|
||||
def my_callback(from_jid, result, nodeId=None, timestamp=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
if result == "failure":
|
||||
callback_data["nodeId"] = nodeId;
|
||||
callback_data["timestamp"] = timestamp;
|
||||
callback_data["error_msg"] = error_msg;
|
||||
callback_data["nodeId"] = nodeId
|
||||
callback_data["timestamp"] = timestamp
|
||||
callback_data["error_msg"] = error_msg
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost",
|
||||
to_jid="you@google.com",
|
||||
nodeIds=['Device33'],
|
||||
callback=my_callback)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
from='tester@localhost'
|
||||
@@ -677,26 +676,26 @@ class TestStreamSensorData(SlixTest):
|
||||
|
||||
self.failUnlessEqual(results, ["accepted","failure"]);
|
||||
# self.assertIn("nodeId", callback_data);
|
||||
self.assertTrue("nodeId" in callback_data);
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33");
|
||||
self.assertTrue("nodeId" in callback_data)
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33")
|
||||
# self.assertIn("timestamp", callback_data);
|
||||
self.assertTrue("timestamp" in callback_data);
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30");
|
||||
self.assertTrue("timestamp" in callback_data)
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
|
||||
# self.assertIn("error_msg", callback_data);
|
||||
self.assertTrue("error_msg" in callback_data);
|
||||
self.failUnlessEqual(callback_data["error_msg"], "Timeout.");
|
||||
self.assertTrue("error_msg" in callback_data)
|
||||
self.failUnlessEqual(callback_data["error_msg"], "Timeout.")
|
||||
|
||||
def testDelayedRequest(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
|
||||
myDevice = Device("Device22")
|
||||
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"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
dtnow = datetime.datetime.now()
|
||||
ts_2sec = datetime.timedelta(0,2)
|
||||
@@ -748,12 +747,12 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
|
||||
myDevice = Device("Device22")
|
||||
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"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
dtnow = datetime.datetime.now()
|
||||
ts_2sec = datetime.timedelta(0,2)
|
||||
@@ -809,13 +808,13 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice = Device("Device44")
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -879,13 +878,13 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice = Device("Device44")
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -949,13 +948,13 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device44");
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"});
|
||||
myDevice = Device("Device44")
|
||||
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"})
|
||||
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
|
||||
|
||||
print("."),
|
||||
|
||||
@@ -1005,17 +1004,17 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
results = [];
|
||||
callback_data = {};
|
||||
results = []
|
||||
callback_data = {}
|
||||
|
||||
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
if result == "fields":
|
||||
callback_data["nodeId"] = nodeId;
|
||||
callback_data["timestamp"] = timestamp;
|
||||
callback_data["error_msg"] = error_msg;
|
||||
callback_data["nodeId"] = nodeId
|
||||
callback_data["timestamp"] = timestamp
|
||||
callback_data["error_msg"] = error_msg
|
||||
for f in fields:
|
||||
callback_data["field_" + f['name']] = f;
|
||||
callback_data["field_" + f['name']] = f
|
||||
|
||||
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost",
|
||||
to_jid="you@google.com",
|
||||
@@ -1073,17 +1072,17 @@ class TestStreamSensorData(SlixTest):
|
||||
|
||||
self.failUnlessEqual(results, ["queued","started","fields","done"]);
|
||||
# self.assertIn("nodeId", callback_data);
|
||||
self.assertTrue("nodeId" in callback_data);
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33");
|
||||
self.assertTrue("nodeId" in callback_data)
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33")
|
||||
# self.assertIn("timestamp", callback_data);
|
||||
self.assertTrue("timestamp" in callback_data);
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02");
|
||||
self.assertTrue("timestamp" in callback_data)
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
|
||||
# self.assertIn("field_Voltage", callback_data);
|
||||
self.assertTrue("field_Voltage" in callback_data);
|
||||
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}});
|
||||
self.assertTrue("field_Voltage" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
|
||||
# self.assertIn("field_TestBool", callback_data);
|
||||
self.assertTrue("field_TestBool" in callback_data);
|
||||
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" });
|
||||
self.assertTrue("field_TestBool" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
|
||||
|
||||
|
||||
def testRequestFieldsCancelAPI(self):
|
||||
@@ -1092,12 +1091,12 @@ class TestStreamSensorData(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
results = [];
|
||||
results = []
|
||||
|
||||
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
|
||||
session = self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback);
|
||||
session = self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -1119,7 +1118,7 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.xmpp['xep_0323'].cancel_request(session=session);
|
||||
self.xmpp['xep_0323'].cancel_request(session=session)
|
||||
|
||||
self.send("""
|
||||
<iq type='get'
|
||||
@@ -1139,19 +1138,19 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnlessEqual(results, ["accepted","cancelled"]);
|
||||
self.failUnlessEqual(results, ["accepted","cancelled"])
|
||||
|
||||
def testDelayedRequestCancel(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0323'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
|
||||
myDevice = Device("Device22")
|
||||
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"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
dtnow = datetime.datetime.now()
|
||||
ts_2sec = datetime.timedelta(0,2)
|
||||
|
@@ -28,7 +28,7 @@ class TestStreamControl(SlixTest):
|
||||
pass
|
||||
|
||||
def _time_now(self):
|
||||
return datetime.datetime.now().replace(microsecond=0).isoformat();
|
||||
return datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||
|
||||
def tearDown(self):
|
||||
self.stream_close()
|
||||
@@ -38,10 +38,10 @@ class TestStreamControl(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15");
|
||||
myDevice = Device("Device22")
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15")
|
||||
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
self.recv("""
|
||||
<iq type='set'
|
||||
@@ -63,23 +63,23 @@ class TestStreamControl(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "17");
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "17")
|
||||
|
||||
def testRequestSetMulti(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15");
|
||||
myDevice._add_control_field(name="Startup", typename="date", value="2013-01-03");
|
||||
myDevice = Device("Device22")
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15")
|
||||
myDevice._add_control_field(name="Startup", typename="date", value="2013-01-03")
|
||||
|
||||
myDevice2 = Device("Device23");
|
||||
myDevice2._add_control_field(name="Temperature", typename="int", value="19");
|
||||
myDevice2._add_control_field(name="Startup", typename="date", value="2013-01-09");
|
||||
myDevice2 = Device("Device23")
|
||||
myDevice2._add_control_field(name="Temperature", typename="int", value="19")
|
||||
myDevice2._add_control_field(name="Startup", typename="date", value="2013-01-09")
|
||||
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice2, commTimeout=0.5);
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice2, commTimeout=0.5)
|
||||
|
||||
self.recv("""
|
||||
<iq type='set'
|
||||
@@ -102,8 +102,8 @@ class TestStreamControl(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "17");
|
||||
self.assertEqual(myDevice2._get_field_value("Temperature"), "19");
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "17")
|
||||
self.assertEqual(myDevice2._get_field_value("Temperature"), "19")
|
||||
|
||||
self.recv("""
|
||||
<iq type='set'
|
||||
@@ -128,20 +128,20 @@ class TestStreamControl(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "20");
|
||||
self.assertEqual(myDevice2._get_field_value("Temperature"), "20");
|
||||
self.assertEqual(myDevice._get_field_value("Startup"), "2013-02-01");
|
||||
self.assertEqual(myDevice2._get_field_value("Startup"), "2013-02-01");
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "20")
|
||||
self.assertEqual(myDevice2._get_field_value("Temperature"), "20")
|
||||
self.assertEqual(myDevice._get_field_value("Startup"), "2013-02-01")
|
||||
self.assertEqual(myDevice2._get_field_value("Startup"), "2013-02-01")
|
||||
|
||||
def testRequestSetFail(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
myDevice = Device("Device23");
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15");
|
||||
myDevice = Device("Device23")
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15")
|
||||
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice, commTimeout=0.5)
|
||||
|
||||
self.recv("""
|
||||
<iq type='set'
|
||||
@@ -166,18 +166,18 @@ class TestStreamControl(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "15");
|
||||
self.assertFalse(myDevice.has_control_field("Voltage", "int"));
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "15")
|
||||
self.assertFalse(myDevice.has_control_field("Voltage", "int"))
|
||||
|
||||
def testDirectSetOk(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15");
|
||||
myDevice = Device("Device22")
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15")
|
||||
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
self.recv("""
|
||||
<message
|
||||
@@ -191,17 +191,17 @@ class TestStreamControl(SlixTest):
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "17");
|
||||
self.assertEqual(myDevice._get_field_value("Temperature"), "17")
|
||||
|
||||
def testDirectSetFail(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
myDevice = Device("Device22");
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15");
|
||||
myDevice = Device("Device22")
|
||||
myDevice._add_control_field(name="Temperature", typename="int", value="15")
|
||||
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
|
||||
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
|
||||
|
||||
self.recv("""
|
||||
<message
|
||||
@@ -223,16 +223,16 @@ class TestStreamControl(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
results = [];
|
||||
results = []
|
||||
|
||||
def my_callback(from_jid, result, nodeIds=None, fields=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
|
||||
fields = []
|
||||
fields.append(("Temperature", "double", "20.5"))
|
||||
fields.append(("TemperatureAlarmSetting", "string", "High"))
|
||||
|
||||
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback);
|
||||
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback)
|
||||
|
||||
self.send("""
|
||||
<iq type='set'
|
||||
@@ -265,16 +265,16 @@ class TestStreamControl(SlixTest):
|
||||
plugins=['xep_0030',
|
||||
'xep_0325'])
|
||||
|
||||
results = [];
|
||||
results = []
|
||||
|
||||
def my_callback(from_jid, result, nodeIds=None, fields=None, error_msg=None):
|
||||
results.append(result);
|
||||
results.append(result)
|
||||
|
||||
fields = []
|
||||
fields.append(("Temperature", "double", "20.5"))
|
||||
fields.append(("TemperatureAlarmSetting", "string", "High"))
|
||||
|
||||
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback);
|
||||
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback)
|
||||
|
||||
self.send("""
|
||||
<iq type='set'
|
||||
@@ -301,12 +301,12 @@ class TestStreamControl(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.assertEqual(results, ["OtherError"]);
|
||||
self.assertEqual(results, ["OtherError"])
|
||||
|
||||
def testServiceDiscoveryClient(self):
|
||||
self.stream_start(mode='client',
|
||||
plugins=['xep_0030',
|
||||
'xep_0325']);
|
||||
'xep_0325'])
|
||||
|
||||
self.recv("""
|
||||
<iq type='get'
|
||||
@@ -331,7 +331,7 @@ class TestStreamControl(SlixTest):
|
||||
def testServiceDiscoveryComponent(self):
|
||||
self.stream_start(mode='component',
|
||||
plugins=['xep_0030',
|
||||
'xep_0325']);
|
||||
'xep_0325'])
|
||||
|
||||
self.recv("""
|
||||
<iq type='get'
|
||||
|
Reference in New Issue
Block a user