summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins
diff options
context:
space:
mode:
authorNathan Fritz <fritzy@netflint.net>2011-08-12 16:36:03 -0700
committerNathan Fritz <fritzy@netflint.net>2011-08-12 16:36:03 -0700
commit8f1d0e7a79b662e5f2849cea6e73716cc887e226 (patch)
treeec2fe16b50effd4ba5787a57de00c514fad4f333 /sleekxmpp/plugins
parent88184ff9556774b1be3dc7fcb97f1f71803d2d61 (diff)
parent9b7ed73f95145f88887d6fc3daa1bd2a9596b943 (diff)
downloadslixmpp-8f1d0e7a79b662e5f2849cea6e73716cc887e226.tar.gz
slixmpp-8f1d0e7a79b662e5f2849cea6e73716cc887e226.tar.bz2
slixmpp-8f1d0e7a79b662e5f2849cea6e73716cc887e226.tar.xz
slixmpp-8f1d0e7a79b662e5f2849cea6e73716cc887e226.zip
Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r--sleekxmpp/plugins/__init__.py2
-rw-r--r--sleekxmpp/plugins/xep_0004.py395
-rw-r--r--sleekxmpp/plugins/xep_0004/__init__.py11
-rw-r--r--sleekxmpp/plugins/xep_0004/dataforms.py60
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/__init__.py10
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/field.py167
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/form.py250
-rw-r--r--sleekxmpp/plugins/xep_0078.py72
-rw-r--r--sleekxmpp/plugins/xep_0078/__init__.py12
-rw-r--r--sleekxmpp/plugins/xep_0078/legacyauth.py108
-rw-r--r--sleekxmpp/plugins/xep_0078/stanza.py43
-rw-r--r--sleekxmpp/plugins/xep_0199/ping.py2
12 files changed, 664 insertions, 468 deletions
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 7fa031ef..c0b1121b 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -9,3 +9,5 @@ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082',
'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199',
'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
+
+# Don't automatically load xep_0078
diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py
deleted file mode 100644
index 5a49d70f..00000000
--- a/sleekxmpp/plugins/xep_0004.py
+++ /dev/null
@@ -1,395 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import copy
-from . import base
-from .. xmlstream.handler.callback import Callback
-from .. xmlstream.matcher.xpath import MatchXPath
-from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
-from .. stanza.message import Message
-
-
-log = logging.getLogger(__name__)
-
-
-class Form(ElementBase):
- namespace = 'jabber:x:data'
- name = 'x'
- plugin_attrib = 'form'
- interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
- sub_interfaces = set(('title',))
- form_types = set(('cancel', 'form', 'result', 'submit'))
-
- def __init__(self, *args, **kwargs):
- title = None
- if 'title' in kwargs:
- title = kwargs['title']
- del kwargs['title']
- ElementBase.__init__(self, *args, **kwargs)
- if title is not None:
- self['title'] = title
- self.field = FieldAccessor(self)
-
- def setup(self, xml=None):
- if ElementBase.setup(self, xml): #if we had to generate xml
- self['type'] = 'form'
-
- def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
- kwtype = kwargs.get('type', None)
- if kwtype is None:
- kwtype = ftype
-
- field = FormField(parent=self)
- field['var'] = var
- field['type'] = kwtype
- field['label'] = label
- field['desc'] = desc
- field['required'] = required
- field['value'] = value
- if options is not None:
- field['options'] = options
- return field
-
- def getXML(self, type='submit'):
- self['type'] = type
- log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
- return self.xml
-
- def fromXML(self, xml):
- log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
- n = Form(xml=xml)
- return n
-
- def addItem(self, values):
- itemXML = ET.Element('{%s}item' % self.namespace)
- self.xml.append(itemXML)
- reported_vars = self['reported'].keys()
- for var in reported_vars:
- fieldXML = ET.Element('{%s}field' % FormField.namespace)
- itemXML.append(fieldXML)
- field = FormField(xml=fieldXML)
- field['var'] = var
- field['value'] = values.get(var, None)
-
- def addReported(self, var, ftype=None, label='', desc='', **kwargs):
- kwtype = kwargs.get('type', None)
- if kwtype is None:
- kwtype = ftype
- 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)
- field = FormField(xml=fieldXML)
- field['var'] = var
- field['type'] = kwtype
- field['label'] = label
- field['desc'] = desc
- return field
-
- def cancel(self):
- self['type'] = 'cancel'
-
- def delFields(self):
- fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- self.xml.remove(fieldXML)
-
- def delInstructions(self):
- instsXML = self.xml.findall('{%s}instructions')
- for instXML in instsXML:
- self.xml.remove(instXML)
-
- def delItems(self):
- itemsXML = self.xml.find('{%s}item' % self.namespace)
- for itemXML in itemsXML:
- self.xml.remove(itemXML)
-
- def delReported(self):
- reportedXML = self.xml.find('{%s}reported' % self.namespace)
- if reportedXML is not None:
- self.xml.remove(reportedXML)
-
- def getFields(self, use_dict=False):
- fields = {} if use_dict else []
- fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- if use_dict:
- fields[field['var']] = field
- else:
- fields.append((field['var'], field))
- return fields
-
- def getInstructions(self):
- instructions = ''
- instsXML = self.xml.findall('{%s}instructions' % self.namespace)
- return "\n".join([instXML.text for instXML in instsXML])
-
- def getItems(self):
- items = []
- itemsXML = self.xml.findall('{%s}item' % self.namespace)
- for itemXML in itemsXML:
- item = {}
- fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- item[field['var']] = field['value']
- items.append(item)
- return items
-
- def getReported(self):
- fields = {}
- fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
- FormField.namespace))
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- fields[field['var']] = field
- return fields
-
- def getValues(self):
- values = {}
- fields = self.getFields(use_dict=True)
- for var in fields:
- values[var] = fields[var]['value']
- return values
-
- def reply(self):
- if self['type'] == 'form':
- self['type'] = 'submit'
- elif self['type'] == 'submit':
- self['type'] = 'result'
-
- def setFields(self, fields, default=None):
- del self['fields']
- for field_data in fields:
- var = field_data[0]
- field = field_data[1]
- field['var'] = var
-
- self.addField(**field)
-
- def setInstructions(self, instructions):
- del self['instructions']
- if instructions in [None, '']:
- return
- instructions = instructions.split('\n')
- for instruction in instructions:
- inst = ET.Element('{%s}instructions' % self.namespace)
- inst.text = instruction
- self.xml.append(inst)
-
- def setItems(self, items):
- for item in items:
- self.addItem(item)
-
- def setReported(self, reported, default=None):
- for var in reported:
- field = reported[var]
- field['var'] = var
- self.addReported(var, **field)
-
- def setValues(self, values):
- fields = self.getFields(use_dict=True)
- for field in values:
- fields[field]['value'] = values[field]
-
- def merge(self, other):
- new = copy.copy(self)
- if type(other) == dict:
- new.setValues(other)
- return new
- nfields = new.getFields(use_dict=True)
- ofields = other.getFields(use_dict=True)
- nfields.update(ofields)
- new.setFields([(x, nfields[x]) for x in nfields])
- return new
-
-class FieldAccessor(object):
- def __init__(self, form):
- self.form = form
-
- def __getitem__(self, key):
- return self.form.getFields(use_dict=True)[key]
-
- def __contains__(self, key):
- return key in self.form.getFields(use_dict=True)
-
- def has_key(self, key):
- return key in self.form.getFields(use_dict=True)
-
-
-class FormField(ElementBase):
- namespace = 'jabber:x:data'
- name = 'field'
- plugin_attrib = 'field'
- interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
- sub_interfaces = set(('desc',))
- field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
- 'list-single', 'text-multi', 'text-private', 'text-single'))
- multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
- multi_line_types = set(('hidden', 'text-multi'))
- option_types = set(('list-multi', 'list-single'))
- true_values = set((True, '1', 'true'))
-
- def addOption(self, label='', value=''):
- if self['type'] in self.option_types:
- opt = FieldOption(parent=self)
- opt['label'] = label
- opt['value'] = value
- else:
- raise ValueError("Cannot add options to a %s field." % self['type'])
-
- def delOptions(self):
- optsXML = self.xml.findall('{%s}option' % self.namespace)
- for optXML in optsXML:
- self.xml.remove(optXML)
-
- def delRequired(self):
- reqXML = self.xml.find('{%s}required' % self.namespace)
- if reqXML is not None:
- self.xml.remove(reqXML)
-
- def delValue(self):
- valsXML = self.xml.findall('{%s}value' % self.namespace)
- for valXML in valsXML:
- self.xml.remove(valXML)
-
- def getAnswer(self):
- return self.getValue()
-
- def getOptions(self):
- options = []
- optsXML = self.xml.findall('{%s}option' % self.namespace)
- for optXML in optsXML:
- opt = FieldOption(xml=optXML)
- options.append({'label': opt['label'], 'value':opt['value']})
- return options
-
- def getRequired(self):
- reqXML = self.xml.find('{%s}required' % self.namespace)
- return reqXML is not None
-
- def getValue(self):
- valsXML = self.xml.findall('{%s}value' % self.namespace)
- if len(valsXML) == 0:
- return None
- elif self['type'] == 'boolean':
- return valsXML[0].text in self.true_values
- elif self['type'] in self.multi_value_types:
- values = []
- for valXML in valsXML:
- if valXML.text is None:
- valXML.text = ''
- values.append(valXML.text)
- if self['type'] == 'text-multi':
- values = "\n".join(values)
- return values
- else:
- return valsXML[0].text
-
- def setAnswer(self, answer):
- self.setValue(answer)
-
- def setFalse(self):
- self.setValue(False)
-
- def setOptions(self, options):
- for value in options:
- if isinstance(value, dict):
- self.addOption(**value)
- else:
- self.addOption(value=value)
-
- def setRequired(self, required):
- exists = self.getRequired()
- if not exists and required:
- self.xml.append(ET.Element('{%s}required' % self.namespace))
- elif exists and not required:
- self.delRequired()
-
- def setTrue(self):
- self.setValue(True)
-
- def setValue(self, value):
- self.delValue()
- valXMLName = '{%s}value' % self.namespace
-
- if self['type'] == 'boolean':
- if value in self.true_values:
- valXML = ET.Element(valXMLName)
- valXML.text = '1'
- self.xml.append(valXML)
- else:
- valXML = ET.Element(valXMLName)
- valXML.text = '0'
- self.xml.append(valXML)
- elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
- if self['type'] in self.multi_line_types and isinstance(value, str):
- value = value.split('\n')
- if not isinstance(value, list):
- value = [value]
- for val in value:
- if self['type'] in ['', None] and val in self.true_values:
- val = '1'
- valXML = ET.Element(valXMLName)
- valXML.text = val
- self.xml.append(valXML)
- else:
- if isinstance(value, list):
- raise ValueError("Cannot add multiple values to a %s field." % self['type'])
- valXML = ET.Element(valXMLName)
- valXML.text = value
- self.xml.append(valXML)
-
-
-class FieldOption(ElementBase):
- namespace = 'jabber:x:data'
- name = 'option'
- plugin_attrib = 'option'
- interfaces = set(('label', 'value'))
- sub_interfaces = set(('value',))
-
-
-class xep_0004(base.base_plugin):
- """
- XEP-0004: Data Forms
- """
-
- def plugin_init(self):
- self.xep = '0004'
- self.description = 'Data Forms'
-
- self.xmpp.registerHandler(
- Callback('Data Form',
- MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
- Form.namespace)),
- self.handle_form))
-
- registerStanzaPlugin(FormField, FieldOption)
- registerStanzaPlugin(Form, FormField)
- registerStanzaPlugin(Message, Form)
-
- def makeForm(self, ftype='form', title='', instructions=''):
- f = Form()
- f['type'] = ftype
- f['title'] = title
- f['instructions'] = instructions
- return f
-
- def post_init(self):
- base.base_plugin.post_init(self)
- self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
-
- def handle_form(self, message):
- self.xmpp.event("message_xform", message)
-
- def buildForm(self, xml):
- return Form(xml=xml)
diff --git a/sleekxmpp/plugins/xep_0004/__init__.py b/sleekxmpp/plugins/xep_0004/__init__.py
new file mode 100644
index 00000000..aad4e15f
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/__init__.py
@@ -0,0 +1,11 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0004.stanza import Form
+from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption
+from sleekxmpp.plugins.xep_0004.dataforms import xep_0004
diff --git a/sleekxmpp/plugins/xep_0004/dataforms.py b/sleekxmpp/plugins/xep_0004/dataforms.py
new file mode 100644
index 00000000..5414be5c
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/dataforms.py
@@ -0,0 +1,60 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import copy
+
+from sleekxmpp.thirdparty import OrderedDict
+
+from sleekxmpp import Message
+from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.plugins.base import base_plugin
+from sleekxmpp.plugins.xep_0004 import stanza
+from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption
+
+
+class xep_0004(base_plugin):
+ """
+ XEP-0004: Data Forms
+ """
+
+ def plugin_init(self):
+ self.xep = '0004'
+ self.description = 'Data Forms'
+ self.stanza = stanza
+
+ self.xmpp.registerHandler(
+ Callback('Data Form',
+ StanzaPath('message/form'),
+ self.handle_form))
+
+ register_stanza_plugin(FormField, FieldOption, iterable=True)
+ register_stanza_plugin(Form, FormField, iterable=True)
+ register_stanza_plugin(Message, Form)
+
+ def make_form(self, ftype='form', title='', instructions=''):
+ f = Form()
+ f['type'] = ftype
+ f['title'] = title
+ f['instructions'] = instructions
+ return f
+
+ def post_init(self):
+ base_plugin.post_init(self)
+ self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
+
+ def handle_form(self, message):
+ self.xmpp.event("message_xform", message)
+
+ def build_form(self, xml):
+ return Form(xml=xml)
+
+
+xep_0004.makeForm = xep_0004.make_form
+xep_0004.buildForm = xep_0004.build_form
diff --git a/sleekxmpp/plugins/xep_0004/stanza/__init__.py b/sleekxmpp/plugins/xep_0004/stanza/__init__.py
new file mode 100644
index 00000000..6ad35298
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/stanza/__init__.py
@@ -0,0 +1,10 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption
+from sleekxmpp.plugins.xep_0004.stanza.form import Form
diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py
new file mode 100644
index 00000000..9bb92311
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/stanza/field.py
@@ -0,0 +1,167 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream import ElementBase, ET
+
+
+class FormField(ElementBase):
+ namespace = 'jabber:x:data'
+ name = 'field'
+ plugin_attrib = 'field'
+ interfaces = set(('answer', 'desc', 'required', 'value',
+ 'options', 'label', 'type', 'var'))
+ sub_interfaces = set(('desc',))
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+ field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
+ 'jid-single', 'list-multi', 'list-single',
+ 'text-multi', 'text-private', 'text-single'))
+
+ true_values = set((True, '1', 'true'))
+ option_types = set(('list-multi', 'list-single'))
+ multi_line_types = set(('hidden', 'text-multi'))
+ multi_value_types = set(('hidden', 'jid-multi',
+ 'list-multi', 'text-multi'))
+
+ def add_option(self, label='', value=''):
+ if self['type'] in self.option_types:
+ opt = FieldOption(parent=self)
+ opt['label'] = label
+ opt['value'] = value
+ else:
+ raise ValueError("Cannot add options to " + \
+ "a %s field." % self['type'])
+
+ def del_options(self):
+ optsXML = self.xml.findall('{%s}option' % self.namespace)
+ for optXML in optsXML:
+ self.xml.remove(optXML)
+
+ def del_required(self):
+ reqXML = self.xml.find('{%s}required' % self.namespace)
+ if reqXML is not None:
+ self.xml.remove(reqXML)
+
+ def del_value(self):
+ valsXML = self.xml.findall('{%s}value' % self.namespace)
+ for valXML in valsXML:
+ self.xml.remove(valXML)
+
+ def get_answer(self):
+ return self['value']
+
+ def get_options(self):
+ options = []
+ optsXML = self.xml.findall('{%s}option' % self.namespace)
+ for optXML in optsXML:
+ opt = FieldOption(xml=optXML)
+ options.append({'label': opt['label'], 'value': opt['value']})
+ return options
+
+ def get_required(self):
+ reqXML = self.xml.find('{%s}required' % self.namespace)
+ return reqXML is not None
+
+ def get_value(self):
+ valsXML = self.xml.findall('{%s}value' % self.namespace)
+ if len(valsXML) == 0:
+ return None
+ elif self['type'] == 'boolean':
+ return valsXML[0].text in self.true_values
+ elif self['type'] in self.multi_value_types:
+ values = []
+ for valXML in valsXML:
+ if valXML.text is None:
+ valXML.text = ''
+ values.append(valXML.text)
+ if self['type'] == 'text-multi':
+ values = "\n".join(values)
+ return values
+ else:
+ return valsXML[0].text
+
+ def set_answer(self, answer):
+ self['value'] = answer
+
+ def set_false(self):
+ self['value'] = False
+
+ def set_options(self, options):
+ for value in options:
+ if isinstance(value, dict):
+ self.add_option(**value)
+ else:
+ self.add_option(value=value)
+
+ def set_required(self, required):
+ exists = self['required']
+ if not exists and required:
+ self.xml.append(ET.Element('{%s}required' % self.namespace))
+ elif exists and not required:
+ del self['required']
+
+ def set_true(self):
+ self['value'] = True
+
+ def set_value(self, value):
+ del self['value']
+ valXMLName = '{%s}value' % self.namespace
+
+ if self['type'] == 'boolean':
+ if value in self.true_values:
+ valXML = ET.Element(valXMLName)
+ valXML.text = '1'
+ self.xml.append(valXML)
+ else:
+ valXML = ET.Element(valXMLName)
+ valXML.text = '0'
+ self.xml.append(valXML)
+ elif self['type'] in self.multi_value_types or not self['type']:
+ if not isinstance(value, list):
+ if self['type'] in self.multi_line_types:
+ value = value.split('\n')
+ else:
+ value = [value]
+ for val in value:
+ if self['type'] in ['', None] and val in self.true_values:
+ val = '1'
+ valXML = ET.Element(valXMLName)
+ valXML.text = val
+ self.xml.append(valXML)
+ else:
+ if isinstance(value, list):
+ raise ValueError("Cannot add multiple values " + \
+ "to a %s field." % self['type'])
+ valXML = ET.Element(valXMLName)
+ valXML.text = value
+ self.xml.append(valXML)
+
+
+class FieldOption(ElementBase):
+ namespace = 'jabber:x:data'
+ name = 'option'
+ plugin_attrib = 'option'
+ interfaces = set(('label', 'value'))
+ sub_interfaces = set(('value',))
+
+
+FormField.addOption = FormField.add_option
+FormField.delOptions = FormField.del_options
+FormField.delRequired = FormField.del_required
+FormField.delValue = FormField.del_value
+FormField.getAnswer = FormField.get_answer
+FormField.getOptions = FormField.get_options
+FormField.getRequired = FormField.get_required
+FormField.getValue = FormField.get_value
+FormField.setAnswer = FormField.set_answer
+FormField.setFalse = FormField.set_false
+FormField.setOptions = FormField.set_options
+FormField.setRequired = FormField.set_required
+FormField.setTrue = FormField.set_true
+FormField.setValue = FormField.set_value
diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py
new file mode 100644
index 00000000..d85266fc
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/stanza/form.py
@@ -0,0 +1,250 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import copy
+import logging
+
+from sleekxmpp.thirdparty import OrderedDict
+
+from sleekxmpp.xmlstream import ElementBase, ET
+from sleekxmpp.plugins.xep_0004.stanza import FormField
+
+
+log = logging.getLogger(__name__)
+
+
+class Form(ElementBase):
+ namespace = 'jabber:x:data'
+ name = 'x'
+ plugin_attrib = 'form'
+ interfaces = set(('fields', 'instructions', 'items',
+ 'reported', 'title', 'type', 'values'))
+ sub_interfaces = set(('title',))
+ form_types = set(('cancel', 'form', 'result', 'submit'))
+
+ def __init__(self, *args, **kwargs):
+ title = None
+ if 'title' in kwargs:
+ title = kwargs['title']
+ del kwargs['title']
+ ElementBase.__init__(self, *args, **kwargs)
+ if title is not None:
+ self['title'] = title
+
+ def setup(self, xml=None):
+ if ElementBase.setup(self, xml):
+ # If we had to generate xml
+ self['type'] = 'form'
+
+ def set_type(self, ftype):
+ self._set_attr('type', ftype)
+ if ftype == 'submit':
+ fields = self['fields']
+ for var in fields:
+ field = fields[var]
+ del field['type']
+ del field['label']
+ del field['desc']
+ del field['required']
+ del field['options']
+ elif ftype == 'cancel':
+ del self['fields']
+
+ def add_field(self, var='', ftype=None, label='', desc='',
+ required=False, value=None, options=None, **kwargs):
+ kwtype = kwargs.get('type', None)
+ if kwtype is None:
+ kwtype = ftype
+
+ field = FormField(parent=self)
+ field['var'] = var
+ field['type'] = kwtype
+ field['value'] = value
+ if self['type'] in ('form', 'result'):
+ field['label'] = label
+ field['desc'] = desc
+ field['required'] = required
+ if options is not None:
+ field['options'] = options
+ else:
+ del field['type']
+ return field
+
+ def getXML(self, type='submit'):
+ self['type'] = type
+ log.warning("Form.getXML() is deprecated API compatibility " + \
+ "with plugins/old_0004.py")
+ return self.xml
+
+ def fromXML(self, xml):
+ log.warning("Form.fromXML() is deprecated API compatibility " + \
+ "with plugins/old_0004.py")
+ n = Form(xml=xml)
+ return n
+
+ def add_item(self, values):
+ itemXML = ET.Element('{%s}item' % self.namespace)
+ self.xml.append(itemXML)
+ reported_vars = self['reported'].keys()
+ for var in reported_vars:
+ fieldXML = ET.Element('{%s}field' % FormField.namespace)
+ itemXML.append(fieldXML)
+ field = FormField(xml=fieldXML)
+ field['var'] = var
+ field['value'] = values.get(var, None)
+
+ def add_reported(self, var, ftype=None, label='', desc='', **kwargs):
+ kwtype = kwargs.get('type', None)
+ if kwtype is None:
+ kwtype = ftype
+ 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)
+ field = FormField(xml=fieldXML)
+ field['var'] = var
+ field['type'] = kwtype
+ field['label'] = label
+ field['desc'] = desc
+ return field
+
+ def cancel(self):
+ self['type'] = 'cancel'
+
+ def del_fields(self):
+ fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
+ for fieldXML in fieldsXML:
+ self.xml.remove(fieldXML)
+
+ def del_instructions(self):
+ instsXML = self.xml.findall('{%s}instructions')
+ for instXML in instsXML:
+ self.xml.remove(instXML)
+
+ def del_items(self):
+ itemsXML = self.xml.find('{%s}item' % self.namespace)
+ for itemXML in itemsXML:
+ self.xml.remove(itemXML)
+
+ def del_reported(self):
+ reportedXML = self.xml.find('{%s}reported' % self.namespace)
+ if reportedXML is not None:
+ self.xml.remove(reportedXML)
+
+ def get_fields(self, use_dict=False):
+ fields = OrderedDict()
+ fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
+ for fieldXML in fieldsXML:
+ field = FormField(xml=fieldXML)
+ fields[field['var']] = field
+ return fields
+
+ def get_instructions(self):
+ instructions = ''
+ instsXML = self.xml.findall('{%s}instructions' % self.namespace)
+ return "\n".join([instXML.text for instXML in instsXML])
+
+ def get_items(self):
+ items = []
+ itemsXML = self.xml.findall('{%s}item' % self.namespace)
+ for itemXML in itemsXML:
+ item = {}
+ fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
+ for fieldXML in fieldsXML:
+ field = FormField(xml=fieldXML)
+ item[field['var']] = field['value']
+ items.append(item)
+ return items
+
+ def get_reported(self):
+ fields = {}
+ xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
+ FormField.namespace))
+ for field in xml:
+ field = FormField(xml=field)
+ fields[field['var']] = field
+ return fields
+
+ def get_values(self):
+ values = {}
+ fields = self['fields']
+ for var in fields:
+ values[var] = fields[var]['value']
+ return values
+
+ def reply(self):
+ if self['type'] == 'form':
+ self['type'] = 'submit'
+ elif self['type'] == 'submit':
+ self['type'] = 'result'
+
+ def set_fields(self, fields):
+ del self['fields']
+ if not isinstance(fields, list):
+ fields = fields.items()
+ for var, field in fields:
+ field['var'] = var
+ self.add_field(**field)
+
+ def set_instructions(self, instructions):
+ del self['instructions']
+ if instructions in [None, '']:
+ return
+ instructions = instructions.split('\n')
+ for instruction in instructions:
+ inst = ET.Element('{%s}instructions' % self.namespace)
+ inst.text = instruction
+ self.xml.append(inst)
+
+ def set_items(self, items):
+ for item in items:
+ self.add_item(item)
+
+ def set_reported(self, reported):
+ for var in reported:
+ field = reported[var]
+ field['var'] = var
+ self.add_reported(var, **field)
+
+ def set_values(self, values):
+ fields = self['fields']
+ for field in values:
+ fields[field]['value'] = values[field]
+
+ def merge(self, other):
+ new = copy.copy(self)
+ if type(other) == dict:
+ new['values'] = other
+ return new
+ nfields = new['fields']
+ ofields = other['fields']
+ nfields.update(ofields)
+ new['fields'] = nfields
+ return new
+
+
+Form.setType = Form.set_type
+Form.addField = Form.add_field
+Form.addItem = Form.add_item
+Form.addReported = Form.add_reported
+Form.delFields = Form.del_fields
+Form.delInstructions = Form.del_instructions
+Form.delItems = Form.del_items
+Form.delReported = Form.del_reported
+Form.getFields = Form.get_fields
+Form.getInstructions = Form.get_instructions
+Form.getItems = Form.get_items
+Form.getReported = Form.get_reported
+Form.getValues = Form.get_values
+Form.setFields = Form.set_fields
+Form.setInstructions = Form.set_instructions
+Form.setItems = Form.set_items
+Form.setReported = Form.set_reported
+Form.setValues = Form.set_values
diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py
deleted file mode 100644
index bb6a4632..00000000
--- a/sleekxmpp/plugins/xep_0078.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-from __future__ import with_statement
-from xml.etree import cElementTree as ET
-import logging
-import hashlib
-from . import base
-
-
-log = logging.getLogger(__name__)
-
-
-class xep_0078(base.base_plugin):
- """
- XEP-0078 NON-SASL Authentication
- """
- def plugin_init(self):
- self.description = "Non-SASL Authentication (broken)"
- self.xep = "0078"
- self.xmpp.add_event_handler("session_start", self.check_stream)
- #disabling until I fix conflict with PLAIN
- #self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", self.auth)
- self.streamid = ''
-
- def check_stream(self, xml):
- self.streamid = xml.attrib['id']
- if xml.get('version', '0') != '1.0':
- self.auth()
-
- def auth(self, xml=None):
- log.debug("Starting jabber:iq:auth Authentication")
- auth_request = self.xmpp.makeIqGet()
- auth_request_query = ET.Element('{jabber:iq:auth}query')
- auth_request.attrib['to'] = self.xmpp.boundjid.host
- username = ET.Element('username')
- username.text = self.xmpp.username
- auth_request_query.append(username)
- auth_request.append(auth_request_query)
- result = auth_request.send()
- rquery = result.find('{jabber:iq:auth}query')
- attempt = self.xmpp.makeIqSet()
- query = ET.Element('{jabber:iq:auth}query')
- resource = ET.Element('resource')
- resource.text = self.xmpp.resource
- query.append(username)
- query.append(resource)
- if rquery.find('{jabber:iq:auth}digest') is None:
- log.warning("Authenticating via jabber:iq:auth Plain.")
- password = ET.Element('password')
- password.text = self.xmpp.password
- query.append(password)
- else:
- log.debug("Authenticating via jabber:iq:auth Digest")
- digest = ET.Element('digest')
- digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest()
- query.append(digest)
- attempt.append(query)
- result = attempt.send()
- if result.attrib['type'] == 'result':
- with self.xmpp.lock:
- self.xmpp.authenticated = True
- self.xmpp.sessionstarted = True
- self.xmpp.event("session_start")
- else:
- log.info("Authentication failed")
- self.xmpp.disconnect()
- self.xmpp.event("failed_auth")
diff --git a/sleekxmpp/plugins/xep_0078/__init__.py b/sleekxmpp/plugins/xep_0078/__init__.py
new file mode 100644
index 00000000..5a2bda77
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078/__init__.py
@@ -0,0 +1,12 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0078 import stanza
+from sleekxmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature
+from sleekxmpp.plugins.xep_0078.legacyauth import xep_0078
+
diff --git a/sleekxmpp/plugins/xep_0078/legacyauth.py b/sleekxmpp/plugins/xep_0078/legacyauth.py
new file mode 100644
index 00000000..bdd2df67
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078/legacyauth.py
@@ -0,0 +1,108 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+import hashlib
+import random
+
+from sleekxmpp.stanza import Iq, StreamFeatures
+from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
+from sleekxmpp.plugins.base import base_plugin
+from sleekxmpp.plugins.xep_0078 import stanza
+
+
+log = logging.getLogger(__name__)
+
+
+class xep_0078(base_plugin):
+
+ """
+ XEP-0078 NON-SASL Authentication
+
+ This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin
+ unless you are forced to use an old XMPP server implementation.
+ """
+
+ def plugin_init(self):
+ self.xep = "0078"
+ self.description = "Non-SASL Authentication"
+ self.stanza = stanza
+
+ self.xmpp.register_feature('auth',
+ self._handle_auth,
+ restart=False,
+ order=self.config.get('order', 15))
+
+ register_stanza_plugin(Iq, stanza.IqAuth)
+ register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
+
+
+ def _handle_auth(self, features):
+ # If we can or have already authenticated with SASL, do nothing.
+ if 'mechanisms' in features['features']:
+ return False
+ if self.xmpp.authenticated:
+ return False
+
+ log.debug("Starting jabber:iq:auth Authentication")
+
+ # Step 1: Request the auth form
+ iq = self.xmpp.Iq()
+ iq['type'] = 'get'
+ iq['to'] = self.xmpp.boundjid.host
+ iq['auth']['username'] = self.xmpp.boundjid.user
+ resp = iq.send(now=True)
+
+ if resp is None or resp['type'] != 'result':
+ log.info("Authentication failed: %s" % resp['error']['condition'])
+ self.xmpp.event('failed_auth', resp, direct=True)
+ self.xmpp.disconnect()
+ return True
+
+ # Step 2: Fill out auth form for either password or digest auth
+ iq = self.xmpp.Iq()
+ iq['type'] = 'set'
+ iq['auth']['username'] = self.xmpp.boundjid.user
+
+ # A resource is required, so create a random one if necessary
+ if self.xmpp.boundjid.resource:
+ iq['auth']['resource'] = self.xmpp.boundjid.resource
+ else:
+ iq['auth']['resource'] = '%s' % random.random()
+
+ if 'digest' in resp['auth']['fields']:
+ log.debug('Authenticating via jabber:iq:auth Digest')
+ if sys.version_info < (3, 0):
+ stream_id = bytes(self.xmpp.stream_id)
+ password = bytes(self.xmpp.password)
+ else:
+ stream_id = bytes(self.xmpp.stream_id, encoding='utf-8')
+ password = bytes(self.xmpp.password, encoding='utf-8')
+
+ digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest()
+ iq['auth']['digest'] = digest
+ else:
+ log.warning('Authenticating via jabber:iq:auth Plain.')
+ iq['auth']['password'] = self.xmpp.password
+
+ # Step 3: Send credentials
+ result = iq.send(now=True)
+ if result is not None and result.attrib['type'] == 'result':
+ self.xmpp.features.add('auth')
+
+ self.xmpp.authenticated = True
+ log.debug("Established Session")
+ self.xmpp.sessionstarted = True
+ self.xmpp.session_started_event.set()
+ self.xmpp.event('session_start')
+ else:
+ log.info("Authentication failed")
+ self.xmpp.disconnect()
+ self.xmpp.event("failed_auth")
+
+ return True
diff --git a/sleekxmpp/plugins/xep_0078/stanza.py b/sleekxmpp/plugins/xep_0078/stanza.py
new file mode 100644
index 00000000..86ba09ad
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078/stanza.py
@@ -0,0 +1,43 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
+
+
+class IqAuth(ElementBase):
+ namespace = 'jabber:iq:auth'
+ name = 'query'
+ plugin_attrib = 'auth'
+ interfaces = set(('fields', 'username', 'password', 'resource', 'digest'))
+ sub_interfaces = set(('username', 'password', 'resource', 'digest'))
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+ def get_fields(self):
+ fields = set()
+ for field in self.sub_interfaces:
+ if self.xml.find('{%s}%s' % (self.namespace, field)) is not None:
+ fields.add(field)
+ return fields
+
+ def set_resource(self, value):
+ self._set_sub_text('resource', value, keep=True)
+
+ def set_password(self, value):
+ self._set_sub_text('password', value, keep=True)
+
+
+class AuthFeature(ElementBase):
+ namespace = 'http://jabber.org/features/iq-auth'
+ name = 'auth'
+ plugin_attrib = 'auth'
+ interfaces = set()
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+
diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py
index d1e08e61..0fa22f8a 100644
--- a/sleekxmpp/plugins/xep_0199/ping.py
+++ b/sleekxmpp/plugins/xep_0199/ping.py
@@ -108,7 +108,7 @@ class xep_0199(base_plugin):
iq -- The ping request.
"""
log.debug("Pinged by %s" % iq['from'])
- iq.reply().enable('ping').send()
+ iq.reply().send()
def send_ping(self, jid, timeout=None, errorfalse=False,
ifrom=None, block=True, callback=None):