diff options
-rwxr-xr-x | setup.py | 9 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0323/device.py | 378 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0323/stanza/sensordata.py | 20 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0325/device.py | 136 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0325/stanza/control.py | 12 | ||||
-rw-r--r-- | sleekxmpp/stanza/iq.py | 15 | ||||
-rw-r--r-- | sleekxmpp/test/sleektest.py | 3 | ||||
-rw-r--r-- | sleekxmpp/util/sasl/mechanisms.py | 2 | ||||
-rw-r--r-- | sleekxmpp/version.py | 4 | ||||
-rw-r--r-- | sleekxmpp/xmlstream/matcher/__init__.py | 1 | ||||
-rw-r--r-- | sleekxmpp/xmlstream/matcher/idsender.py | 47 | ||||
-rw-r--r-- | tests/test_stream_handlers.py | 53 | ||||
-rw-r--r-- | tests/test_stream_xep_0323.py | 22 |
13 files changed, 408 insertions, 294 deletions
@@ -42,6 +42,7 @@ CLASSIFIERS = [ 'Intended Audience :: Developers', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', 'Topic :: Software Development :: Libraries :: Python Modules', ] @@ -86,8 +87,8 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0086', 'sleekxmpp/plugins/xep_0091', 'sleekxmpp/plugins/xep_0092', - 'sleekxmpp/plugins/xep_0095', - 'sleekxmpp/plugins/xep_0096', + 'sleekxmpp/plugins/xep_0095', + 'sleekxmpp/plugins/xep_0096', 'sleekxmpp/plugins/xep_0107', 'sleekxmpp/plugins/xep_0108', 'sleekxmpp/plugins/xep_0115', @@ -118,6 +119,10 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0308', 'sleekxmpp/plugins/xep_0313', 'sleekxmpp/plugins/xep_0319', + 'sleekxmpp/plugins/xep_0323', + 'sleekxmpp/plugins/xep_0323/stanza', + 'sleekxmpp/plugins/xep_0325', + 'sleekxmpp/plugins/xep_0325/stanza', 'sleekxmpp/plugins/google', 'sleekxmpp/plugins/google/gmail', 'sleekxmpp/plugins/google/auth', diff --git a/sleekxmpp/plugins/xep_0323/device.py b/sleekxmpp/plugins/xep_0323/device.py index 23bdd153..0bc20327 100644 --- a/sleekxmpp/plugins/xep_0323/device.py +++ b/sleekxmpp/plugins/xep_0323/device.py @@ -12,51 +12,51 @@ import datetime import logging class Device(object): - """ - Example implementation of a device readout object. + """ + Example implementation of a device readout object. Is registered in the XEP_0323.register_node call The device object may be any custom implementation to support specific devices, but it must implement the functions: has_field request_fields - """ - - def __init__(self, nodeId, fields={}): - self.nodeId = nodeId - self.fields = fields # see fields described below - # {'type':'numeric', - # 'name':'myname', - # 'value': 42, - # 'unit':'Z'}]; - self.timestamp_data = {} - self.momentary_data = {} - self.momentary_timestamp = "" - logging.debug("Device object started nodeId %s",nodeId) - - def has_field(self, field): - """ - Returns true if the supplied field name exists in this device. + """ + + def __init__(self, nodeId, fields={}): + self.nodeId = nodeId + self.fields = fields # see fields described below + # {'type':'numeric', + # 'name':'myname', + # 'value': 42, + # 'unit':'Z'}]; + self.timestamp_data = {} + self.momentary_data = {} + self.momentary_timestamp = "" + logging.debug("Device object started nodeId %s",nodeId) + + def has_field(self, field): + """ + Returns true if the supplied field name exists in this device. Arguments: - field -- The field name - """ - if field in self.fields.keys(): - return True; - return False; - - def refresh(self, fields): - """ - override method to do the refresh work - refresh values from hardware or other - """ - pass - - - def request_fields(self, fields, flags, session, callback): - """ - Starts a data readout. Verifies the requested fields, - refreshes the data (if needed) and calls the callback - with requested data. + field -- The field name + """ + if field in self.fields.keys(): + return True; + return False; + + def refresh(self, fields): + """ + override method to do the refresh work + refresh values from hardware or other + """ + pass + + + def request_fields(self, fields, flags, session, callback): + """ + Starts a data readout. Verifies the requested fields, + refreshes the data (if needed) and calls the callback + with requested data. Arguments: @@ -65,153 +65,153 @@ class Device(object): Formatted as a dictionary like { "flag name": "flag value" ... } session -- Session id, only used in the callback as identifier callback -- Callback function to call when data is available. - - The callback function must support the following arguments: + + The callback function must support the following arguments: - session -- Session id, as supplied in the request_fields call - nodeId -- Identifier for this device - result -- The current result status of the readout. Valid values are: + session -- Session id, as supplied in the request_fields call + nodeId -- Identifier for this device + result -- The current result status of the readout. Valid values are: "error" - Readout failed. "fields" - Contains readout data. "done" - Indicates that the readout is complete. May contain readout data. - timestamp_block -- [optional] Only applies when result != "error" + timestamp_block -- [optional] Only applies when result != "error" The readout data. Structured as a dictionary: - { - timestamp: timestamp for this datablock, - fields: list of field dictionary (one per readout field). - readout field dictionary format: - { - type: The field type (numeric, boolean, dateTime, timeSpan, string, enum) - name: The field name - value: The field value - unit: The unit of the field. Only applies to type numeric. - dataType: The datatype of the field. Only applies to type enum. - flags: [optional] data classifier flags for the field, e.g. momentary + { + timestamp: timestamp for this datablock, + fields: list of field dictionary (one per readout field). + readout field dictionary format: + { + type: The field type (numeric, boolean, dateTime, timeSpan, string, enum) + name: The field name + value: The field value + unit: The unit of the field. Only applies to type numeric. + dataType: The datatype of the field. Only applies to type enum. + flags: [optional] data classifier flags for the field, e.g. momentary Formatted as a dictionary like { "flag name": "flag value" ... } - } - } - error_msg -- [optional] Only applies when result == "error". + } + } + error_msg -- [optional] Only applies when result == "error". Error details when a request failed. - """ - logging.debug("request_fields called looking for fields %s",fields) - if len(fields) > 0: - # Check availiability - for f in fields: - if f not in self.fields.keys(): - self._send_reject(session, callback) - return False; - else: - # Request all fields - fields = self.fields.keys(); - - - # Refresh data from device - # ... - logging.debug("about to refresh device fields %s",fields) - self.refresh(fields) - - if "momentary" in flags and flags['momentary'] == "true" or \ - "all" in flags and flags['all'] == "true": - ts_block = {}; - timestamp = ""; - - if len(self.momentary_timestamp) > 0: - timestamp = self.momentary_timestamp; - else: - timestamp = self._get_timestamp(); - - field_block = []; - for f in self.momentary_data: - if f in fields: - field_block.append({"name": f, - "type": self.fields[f]["type"], - "unit": self.fields[f]["unit"], - "dataType": self.fields[f]["dataType"], - "value": self.momentary_data[f]["value"], - "flags": self.momentary_data[f]["flags"]}); - ts_block["timestamp"] = timestamp; - ts_block["fields"] = field_block; - - callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block); - return - - from_flag = self._datetime_flag_parser(flags, 'from') - to_flag = self._datetime_flag_parser(flags, 'to') - - for ts in sorted(self.timestamp_data.keys()): - tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S") - if not from_flag is None: - if tsdt < from_flag: - #print (str(tsdt) + " < " + str(from_flag)) - continue - if not to_flag is None: - if tsdt > to_flag: - #print (str(tsdt) + " > " + str(to_flag)) - continue - - ts_block = {}; - field_block = []; - - for f in self.timestamp_data[ts]: - if f in fields: - field_block.append({"name": f, - "type": self.fields[f]["type"], - "unit": self.fields[f]["unit"], - "dataType": self.fields[f]["dataType"], - "value": self.timestamp_data[ts][f]["value"], - "flags": self.timestamp_data[ts][f]["flags"]}); - - ts_block["timestamp"] = ts; - ts_block["fields"] = field_block; - callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block); - callback(session, result="done", nodeId=self.nodeId, timestamp_block=None); - - def _datetime_flag_parser(self, flags, flagname): - if not flagname in flags: - return None - - dt = None - try: - dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S") - except ValueError: - # Badly formatted datetime, ignore it - pass - return dt - - - def _get_timestamp(self): - """ - Generates a properly formatted timestamp of current time - """ - return datetime.datetime.now().replace(microsecond=0).isoformat() - - def _send_reject(self, session, callback): - """ - Sends a reject to the caller + """ + logging.debug("request_fields called looking for fields %s",fields) + if len(fields) > 0: + # Check availiability + for f in fields: + if f not in self.fields.keys(): + self._send_reject(session, callback) + return False; + else: + # Request all fields + fields = self.fields.keys(); + + + # Refresh data from device + # ... + logging.debug("about to refresh device fields %s",fields) + self.refresh(fields) + + if "momentary" in flags and flags['momentary'] == "true" or \ + "all" in flags and flags['all'] == "true": + ts_block = {}; + timestamp = ""; + + if len(self.momentary_timestamp) > 0: + timestamp = self.momentary_timestamp; + else: + timestamp = self._get_timestamp(); + + field_block = []; + for f in self.momentary_data: + if f in fields: + field_block.append({"name": f, + "type": self.fields[f]["type"], + "unit": self.fields[f]["unit"], + "dataType": self.fields[f]["dataType"], + "value": self.momentary_data[f]["value"], + "flags": self.momentary_data[f]["flags"]}); + ts_block["timestamp"] = timestamp; + ts_block["fields"] = field_block; + + callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block); + return + + from_flag = self._datetime_flag_parser(flags, 'from') + to_flag = self._datetime_flag_parser(flags, 'to') + + for ts in sorted(self.timestamp_data.keys()): + tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S") + if not from_flag is None: + if tsdt < from_flag: + #print (str(tsdt) + " < " + str(from_flag)) + continue + if not to_flag is None: + if tsdt > to_flag: + #print (str(tsdt) + " > " + str(to_flag)) + continue + + ts_block = {}; + field_block = []; + + for f in self.timestamp_data[ts]: + if f in fields: + field_block.append({"name": f, + "type": self.fields[f]["type"], + "unit": self.fields[f]["unit"], + "dataType": self.fields[f]["dataType"], + "value": self.timestamp_data[ts][f]["value"], + "flags": self.timestamp_data[ts][f]["flags"]}); + + ts_block["timestamp"] = ts; + ts_block["fields"] = field_block; + callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block); + callback(session, result="done", nodeId=self.nodeId, timestamp_block=None); + + def _datetime_flag_parser(self, flags, flagname): + if not flagname in flags: + return None + + dt = None + try: + dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S") + except ValueError: + # Badly formatted datetime, ignore it + pass + return dt + + + def _get_timestamp(self): + """ + Generates a properly formatted timestamp of current time + """ + return datetime.datetime.now().replace(microsecond=0).isoformat() + + def _send_reject(self, session, callback): + """ + Sends a reject to the caller Arguments: session -- Session id, see definition in request_fields function callback -- Callback function, see definition in request_fields function - """ - callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject"); + """ + callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject"); - def _add_field(self, name, typename, unit=None, dataType=None): - """ - Adds a field to the device + def _add_field(self, name, typename, unit=None, dataType=None): + """ + Adds a field to the device Arguments: name -- Name of the field typename -- Type of the field (numeric, boolean, dateTime, timeSpan, string, enum) unit -- [optional] only applies to "numeric". Unit for the field. dataType -- [optional] only applies to "enum". Datatype for the field. - """ - self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType}; + """ + self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType}; - def _add_field_timestamp_data(self, name, timestamp, value, flags=None): - """ - Adds timestamped data to a field + def _add_field_timestamp_data(self, name, timestamp, value, flags=None): + """ + Adds timestamped data to a field Arguments: name -- Name of the field @@ -219,37 +219,37 @@ class Device(object): value -- Field value at the timestamp flags -- [optional] data classifier flags for the field, e.g. momentary Formatted as a dictionary like { "flag name": "flag value" ... } - """ - if not name in self.fields.keys(): - return False; - if not timestamp in self.timestamp_data: - self.timestamp_data[timestamp] = {}; + """ + if not name in self.fields.keys(): + return False; + if not timestamp in self.timestamp_data: + self.timestamp_data[timestamp] = {}; - self.timestamp_data[timestamp][name] = {"value": value, "flags": flags}; - return True; + self.timestamp_data[timestamp][name] = {"value": value, "flags": flags}; + return True; - def _add_field_momentary_data(self, name, value, flags=None): - """ - Sets momentary data to a field + def _add_field_momentary_data(self, name, value, flags=None): + """ + Sets momentary data to a field Arguments: name -- Name of the field value -- Field value at the timestamp flags -- [optional] data classifier flags for the field, e.g. momentary Formatted as a dictionary like { "flag name": "flag value" ... } - """ - if not self.fields.has_key(name): - return False; - if flags is None: - flags = {}; - - flags["momentary"] = "true" - self.momentary_data[name] = {"value": value, "flags": flags}; - return True; - - def _set_momentary_timestamp(self, timestamp): - """ - This function is only for unit testing to produce predictable results. - """ - self.momentary_timestamp = timestamp; + """ + if name not in self.fields: + return False; + if flags is None: + flags = {}; + + flags["momentary"] = "true" + self.momentary_data[name] = {"value": value, "flags": flags}; + return True; + + def _set_momentary_timestamp(self, timestamp): + """ + This function is only for unit testing to produce predictable results. + """ + self.momentary_timestamp = timestamp; diff --git a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py b/sleekxmpp/plugins/xep_0323/stanza/sensordata.py index 55e5060f..a11c3e94 100644 --- a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py +++ b/sleekxmpp/plugins/xep_0323/stanza/sensordata.py @@ -124,10 +124,10 @@ class Request(ElementBase): def get_nodes(self): """Return all nodes.""" - nodes = set() + nodes = [] for node in self['substanzas']: if isinstance(node, RequestNode): - nodes.add(node) + nodes.append(node) return nodes def set_nodes(self, nodes): @@ -190,10 +190,10 @@ class Request(ElementBase): def get_fields(self): """Return all fields.""" - fields = set() + fields = [] for field in self['substanzas']: if isinstance(field, RequestField): - fields.add(field) + fields.append(field) return fields def set_fields(self, fields): @@ -351,10 +351,10 @@ class Fields(ElementBase): def get_nodes(self): """Return all nodes.""" - nodes = set() + nodes = [] for node in self['substanzas']: if isinstance(node, FieldsNode): - nodes.add(node) + nodes.append(node) return nodes def set_nodes(self, nodes): @@ -450,10 +450,10 @@ class FieldsNode(ElementBase): def get_timestamps(self): """Return all timestamps.""" #print(str(id(self)) + " get_timestamps: ") - timestamps = set() + timestamps = [] for timestamp in self['substanzas']: if isinstance(timestamp, Timestamp): - timestamps.add(timestamp) + timestamps.append(timestamp) return timestamps def set_timestamps(self, timestamps): @@ -634,10 +634,10 @@ class Timestamp(ElementBase): def get_datas(self): """ Return all data elements. """ - datas = set() + datas = [] for data in self['substanzas']: if isinstance(data, Field): - datas.add(data) + datas.append(data) return datas def set_datas(self, datas): diff --git a/sleekxmpp/plugins/xep_0325/device.py b/sleekxmpp/plugins/xep_0325/device.py index 1cb99510..a60d5f9a 100644 --- a/sleekxmpp/plugins/xep_0325/device.py +++ b/sleekxmpp/plugins/xep_0325/device.py @@ -11,36 +11,36 @@ import datetime class Device(object): - """ - Example implementation of a device control object. + """ + Example implementation of a device control object. The device object may by any custom implementation to support specific devices, but it must implement the functions: has_control_field set_control_fields - """ + """ - def __init__(self, nodeId): - self.nodeId = nodeId; - self.control_fields = {}; + def __init__(self, nodeId): + self.nodeId = nodeId; + self.control_fields = {}; - def has_control_field(self, field, typename): - """ - Returns true if the supplied field name exists - and the type matches for control in this device. + def has_control_field(self, field, typename): + """ + Returns true if the supplied field name exists + and the type matches for control in this device. Arguments: - field -- The field name + field -- The field name typename -- The expected type - """ - if field in self.control_fields and self.control_fields[field]["type"] == typename: - return True; - return False; + """ + if field in self.control_fields and self.control_fields[field]["type"] == typename: + return True; + return False; - def set_control_fields(self, fields, session, callback): - """ - Starts a control setting procedure. Verifies the fields, - sets the data and (if needed) and calls the callback. + def set_control_fields(self, fields, session, callback): + """ + Starts a control setting procedure. Verifies the fields, + sets the data and (if needed) and calls the callback. Arguments: fields -- List of control fields in tuple format: @@ -48,50 +48,50 @@ class Device(object): session -- Session id, only used in the callback as identifier callback -- Callback function to call when control set is complete. - The callback function must support the following arguments: + The callback function must support the following arguments: - session -- Session id, as supplied in the - request_fields call - nodeId -- Identifier for this device - result -- The current result status of the readout. - Valid values are: + session -- Session id, as supplied in the + request_fields call + nodeId -- Identifier for this device + result -- The current result status of the readout. + Valid values are: "error" - Set fields failed. "ok" - All fields were set. - error_field -- [optional] Only applies when result == "error" - The field name that failed - (usually means it is missing) - error_msg -- [optional] Only applies when result == "error". - Error details when a request failed. - """ - - if len(fields) > 0: - # Check availiability - for name, typename, value in fields: - if not self.has_control_field(name, typename): - self._send_control_reject(session, name, "NotFound", callback) - return False; - - for name, typename, value in fields: - self._set_field_value(name, value) - - callback(session, result="ok", nodeId=self.nodeId); - return True - - def _send_control_reject(self, session, field, message, callback): - """ - Sends a reject to the caller + error_field -- [optional] Only applies when result == "error" + The field name that failed + (usually means it is missing) + error_msg -- [optional] Only applies when result == "error". + Error details when a request failed. + """ + + if len(fields) > 0: + # Check availiability + for name, typename, value in fields: + if not self.has_control_field(name, typename): + self._send_control_reject(session, name, "NotFound", callback) + return False; + + for name, typename, value in fields: + self._set_field_value(name, value) + + callback(session, result="ok", nodeId=self.nodeId); + return True + + def _send_control_reject(self, session, field, message, callback): + """ + Sends a reject to the caller Arguments: session -- Session id, see definition in set_control_fields function callback -- Callback function, see definition in set_control_fields function - """ - callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message); + """ + callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message); - def _add_control_field(self, name, typename, value): - """ - Adds a control field to the device + def _add_control_field(self, name, typename, value): + """ + Adds a control field to the device Arguments: name -- Name of the field @@ -99,27 +99,27 @@ class Device(object): (boolean, color, string, date, dateTime, double, duration, int, long, time) value -- Field value - """ - self.control_fields[name] = {"type": typename, "value": value}; + """ + self.control_fields[name] = {"type": typename, "value": value}; - def _set_field_value(self, name, value): - """ - Set the value of a control field + def _set_field_value(self, name, value): + """ + Set the value of a control field Arguments: name -- Name of the field value -- New value for the field - """ - if name in self.control_fields: - self.control_fields[name]["value"] = value; + """ + if name in self.control_fields: + self.control_fields[name]["value"] = value; - def _get_field_value(self, name): - """ - Get the value of a control field. Only used for unit testing. + def _get_field_value(self, name): + """ + Get the value of a control field. Only used for unit testing. Arguments: name -- Name of the field - """ - if name in self.control_fields: - return self.control_fields[name]["value"]; - return None; + """ + if name in self.control_fields: + return self.control_fields[name]["value"]; + return None; diff --git a/sleekxmpp/plugins/xep_0325/stanza/control.py b/sleekxmpp/plugins/xep_0325/stanza/control.py index 2707191f..67107ecb 100644 --- a/sleekxmpp/plugins/xep_0325/stanza/control.py +++ b/sleekxmpp/plugins/xep_0325/stanza/control.py @@ -83,10 +83,10 @@ class ControlSet(ElementBase): def get_nodes(self): """Return all nodes.""" - nodes = set() + nodes = [] for node in self['substanzas']: if isinstance(node, RequestNode): - nodes.add(node) + nodes.append(node) return nodes def set_nodes(self, nodes): @@ -175,10 +175,10 @@ class ControlSet(ElementBase): def get_datas(self): """ Return all data elements. """ - datas = set() + datas = [] for data in self['substanzas']: if isinstance(data, BaseParameter): - datas.add(data) + datas.append(data) return datas def set_datas(self, datas): @@ -274,10 +274,10 @@ class ControlSetResponse(ElementBase): def get_nodes(self): """Return all nodes.""" - nodes = set() + nodes = [] for node in self['substanzas']: if isinstance(node, RequestNode): - nodes.add(node) + nodes.append(node) return nodes def set_nodes(self, nodes): diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index ba945e08..e377b82f 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -9,7 +9,7 @@ from sleekxmpp.stanza.rootstanza import RootStanza from sleekxmpp.xmlstream import StanzaBase, ET from sleekxmpp.xmlstream.handler import Waiter, Callback -from sleekxmpp.xmlstream.matcher import MatcherId +from sleekxmpp.xmlstream.matcher import MatchIDSender from sleekxmpp.exceptions import IqTimeout, IqError @@ -193,6 +193,13 @@ class Iq(RootStanza): """ if timeout is None: timeout = self.stream.response_timeout + + criteria = { + 'id': self['id'], + 'self': self.stream.boundjid, + 'peer': self['to'] + } + if callback is not None and self['type'] in ('get', 'set'): handler_name = 'IqCallback_%s' % self['id'] if timeout_callback: @@ -203,19 +210,19 @@ class Iq(RootStanza): self._fire_timeout, repeat=False) handler = Callback(handler_name, - MatcherId(self['id']), + MatchIDSender(criteria), self._handle_result, once=True) else: handler = Callback(handler_name, - MatcherId(self['id']), + MatchIDSender(criteria), callback, once=True) self.stream.register_handler(handler) StanzaBase.send(self, now=now) return handler_name elif block and self['type'] in ('get', 'set'): - waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) + waitfor = Waiter('IqWait_%s' % self['id'], MatchIDSender(criteria)) self.stream.register_handler(waitfor) StanzaBase.send(self, now=now) result = waitfor.wait(timeout) diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index 04fb106d..51cda3ee 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -16,7 +16,7 @@ from sleekxmpp.test import TestSocket, TestLiveSocket from sleekxmpp.xmlstream import ET from sleekxmpp.xmlstream import ElementBase from sleekxmpp.xmlstream.tostring import tostring -from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId +from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId, MatchIDSender from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath @@ -212,6 +212,7 @@ class SleekTest(unittest.TestCase): matchers = {'stanzapath': StanzaPath, 'xpath': MatchXPath, 'mask': MatchXMLMask, + 'idsender': MatchIDSender, 'id': MatcherId} Matcher = matchers.get(method, None) if Matcher is None: diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py index 1eb6af83..523eabc2 100644 --- a/sleekxmpp/util/sasl/mechanisms.py +++ b/sleekxmpp/util/sasl/mechanisms.py @@ -111,7 +111,7 @@ class X_FACEBOOK_PLATFORM(Mech): b'api_key': self.credentials['api_key'] } - resp = '&'.join(['%s=%s' % (k, v) for k, v in resp_data.items()]) + resp = '&'.join(['%s=%s' % (k.decode("utf-8"), v.decode("utf-8")) for k, v in resp_data.items()]) return bytes(resp) return b'' diff --git a/sleekxmpp/version.py b/sleekxmpp/version.py index 6874ce6d..33f7ccdd 100644 --- a/sleekxmpp/version.py +++ b/sleekxmpp/version.py @@ -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.2.0' -__version_info__ = (1, 2, 0, '', 0) +__version__ = '1.2.2' +__version_info__ = (1, 2, 2, '', 0) diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py index 1038d1bd..aa74c434 100644 --- a/sleekxmpp/xmlstream/matcher/__init__.py +++ b/sleekxmpp/xmlstream/matcher/__init__.py @@ -7,6 +7,7 @@ """ from sleekxmpp.xmlstream.matcher.id import MatcherId +from sleekxmpp.xmlstream.matcher.idsender import MatchIDSender from sleekxmpp.xmlstream.matcher.many import MatchMany from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask diff --git a/sleekxmpp/xmlstream/matcher/idsender.py b/sleekxmpp/xmlstream/matcher/idsender.py new file mode 100644 index 00000000..5c2c1f51 --- /dev/null +++ b/sleekxmpp/xmlstream/matcher/idsender.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" + sleekxmpp.xmlstream.matcher.id + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Part of SleekXMPP: The Sleek XMPP Library + + :copyright: (c) 2011 Nathanael C. Fritz + :license: MIT, see LICENSE for more details +""" + +from sleekxmpp.xmlstream.matcher.base import MatcherBase + + +class MatchIDSender(MatcherBase): + + """ + The IDSender matcher selects stanzas that have the same stanza 'id' + interface value as the desired ID, and that the 'from' value is one + of a set of approved entities that can respond to a request. + """ + + def match(self, xml): + """Compare the given stanza's ``'id'`` attribute to the stored + ``id`` value, and verify the sender's JID. + + :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` + stanza to compare against. + """ + + selfjid = self._criteria['self'] + peerjid = self._criteria['peer'] + + allowed = {} + allowed[''] = True + allowed[selfjid.bare] = True + allowed[selfjid.host] = True + allowed[peerjid.full] = True + allowed[peerjid.bare] = True + allowed[peerjid.host] = True + + _from = xml['from'] + + try: + return xml['id'] == self._criteria['id'] and allowed[_from] + except KeyError: + return False diff --git a/tests/test_stream_handlers.py b/tests/test_stream_handlers.py index ef46da6a..0208cd16 100644 --- a/tests/test_stream_handlers.py +++ b/tests/test_stream_handlers.py @@ -1,4 +1,5 @@ import time +import threading import unittest from sleekxmpp.test import SleekTest @@ -226,5 +227,57 @@ class TestHandlers(SleekTest): </message> """) + def testWrongSender(self): + """ + Test that using the wrong sender JID in a IQ result + doesn't trigger handlers. + """ + + events = [] + + def run_test(): + # Check that Iq was sent by waiter_handler + iq = self.Iq() + iq['id'] = 'test' + iq['to'] = 'tester@sleekxmpp.com/test' + iq['type'] = 'set' + iq['query'] = 'test' + result = iq.send() + events.append(result['from'].full) + + t = threading.Thread(name="sender_test", target=run_test) + t.start() + + self.recv(""" + <iq id="test" from="evil@sleekxmpp.com/bad" type="result"> + <query xmlns="test" /> + </iq> + """) + self.recv(""" + <iq id="test" from="evil2@sleekxmpp.com" type="result"> + <query xmlns="test" /> + </iq> + """) + self.recv(""" + <iq id="test" from="evil.com" type="result"> + <query xmlns="test" /> + </iq> + """) + + # Now for a good one + self.recv(""" + <iq id="test" from="tester@sleekxmpp.com/test" type="result"> + <query xmlns="test" /> + </iq> + """) + + t.join() + + time.sleep(0.1) + + self.assertEqual(events, ['tester@sleekxmpp.com/test'], "Did not timeout on bad sender") + + + suite = unittest.TestLoader().loadTestsFromTestCase(TestHandlers) diff --git a/tests/test_stream_xep_0323.py b/tests/test_stream_xep_0323.py index b10f0b0d..fd2ad225 100644 --- a/tests/test_stream_xep_0323.py +++ b/tests/test_stream_xep_0323.py @@ -572,16 +572,16 @@ class TestStreamSensorData(SleekTest): self.failUnlessEqual(results, ["accepted","fields","done"]); # self.assertIn("nodeId", callback_data); - self.assertTrue(callback_data.has_key("nodeId")); + self.assertTrue("nodeId" in callback_data) self.failUnlessEqual(callback_data["nodeId"], "Device33"); # self.assertIn("timestamp", callback_data); - self.assertTrue(callback_data.has_key("timestamp")); + self.assertTrue("timestamp" in callback_data); self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02"); #self.assertIn("field_Voltage", callback_data); - self.assertTrue(callback_data.has_key("field_Voltage")); + 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(callback_data.has_key("field_TestBool")); + self.assertTrue("field_TestBool" in callback_data); self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" }); def testServiceDiscoveryClient(self): @@ -693,13 +693,13 @@ class TestStreamSensorData(SleekTest): self.failUnlessEqual(results, ["accepted","failure"]); # self.assertIn("nodeId", callback_data); - self.assertTrue(callback_data.has_key("nodeId")); + self.assertTrue("nodeId" in callback_data); self.failUnlessEqual(callback_data["nodeId"], "Device33"); # self.assertIn("timestamp", callback_data); - self.assertTrue(callback_data.has_key("timestamp")); + self.assertTrue("timestamp" in callback_data); self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30"); # self.assertIn("error_msg", callback_data); - self.assertTrue(callback_data.has_key("error_msg")); + self.assertTrue("error_msg" in callback_data); self.failUnlessEqual(callback_data["error_msg"], "Timeout."); def testDelayedRequest(self): @@ -1095,16 +1095,16 @@ class TestStreamSensorData(SleekTest): self.failUnlessEqual(results, ["queued","started","fields","done"]); # self.assertIn("nodeId", callback_data); - self.assertTrue(callback_data.has_key("nodeId")); + self.assertTrue("nodeId" in callback_data); self.failUnlessEqual(callback_data["nodeId"], "Device33"); # self.assertIn("timestamp", callback_data); - self.assertTrue(callback_data.has_key("timestamp")); + self.assertTrue("timestamp" in callback_data); self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02"); # self.assertIn("field_Voltage", callback_data); - self.assertTrue(callback_data.has_key("field_Voltage")); + 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(callback_data.has_key("field_TestBool")); + self.assertTrue("field_TestBool" in callback_data); self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" }); |