├── .gitignore ├── CHANGES ├── COPYING ├── MANIFEST.in ├── Makefile ├── README ├── README.WIN32 ├── README.rst ├── TODO ├── TODO.pylint ├── auxtools ├── code2xmi.py ├── cosmetics.sh ├── cosmetics.vim ├── htmlmerge.py ├── pylint.sh ├── pylintrc ├── users └── xmimerge.py ├── build.cfg ├── configure.py ├── doc ├── .gitignore ├── Makefile ├── pyxmpp.xmi ├── template.html └── www │ ├── index.html │ └── style.css ├── examples ├── c2s_test.py ├── echobot.py ├── echobot_old.py ├── echocomponent.py ├── getcert.py ├── send_message.py └── server_c2s.py ├── ext ├── Copyright-libxml2 └── xmlextra.c ├── pyxmpp ├── .gitignore ├── __init__.py ├── all.py ├── cache.py ├── client.py ├── clientstream.py ├── error.py ├── exceptions.py ├── expdict.py ├── interface.py ├── interface_micro_impl.py ├── interfaces.py ├── iq.py ├── jabber │ ├── __init__.py │ ├── all.py │ ├── client.py │ ├── clientstream.py │ ├── dataforms.py │ ├── delay.py │ ├── disco.py │ ├── muc.py │ ├── muccore.py │ ├── register.py │ ├── simple.py │ └── vcard.py ├── jabberd │ ├── __init__.py │ ├── all.py │ ├── component.py │ └── componentstream.py ├── jid.py ├── message.py ├── objects.py ├── presence.py ├── resolver.py ├── roster.py ├── sasl │ ├── __init__.py │ ├── core.py │ ├── digest_md5.py │ ├── external.py │ ├── gssapi.py │ └── plain.py ├── stanza.py ├── stanzaprocessor.py ├── stream.py ├── streambase.py ├── streamsasl.py ├── streamtls.py ├── utils.py ├── xmlextra.py └── xmppstringprep.py ├── setup.py ├── tests ├── Makefile ├── all.py ├── cache.py ├── data │ ├── disco_info_in.txt │ ├── disco_info_in.xml │ ├── disco_items_in.txt │ ├── disco_items_in.xml │ ├── disco_items_out.xml │ ├── stream.xml │ ├── stream_info.txt │ ├── vcard1.txt │ ├── vcard1.xml │ ├── vcard2.txt │ ├── vcard2.vcf │ ├── vcard3.txt │ ├── vcard3.vcf │ ├── vcard_with_semicolon.xml │ ├── vcard_without_fn.txt │ ├── vcard_without_fn.xml │ ├── vcard_without_n.txt │ └── vcard_without_n.xml ├── dataforms.py ├── disco.py ├── imports.py ├── interface.py ├── jid.py ├── message.py ├── ns_operations.py ├── presence.py ├── register.py ├── stream_reader.py └── vcard.py └── utils └── migrate-0_5-0_6.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | tests/pyxmpp 4 | examples/pyxmpp 5 | ChangeLog 6 | MANIFEST 7 | cl-stamp 8 | dist/ 9 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Most Important API Changes 2 | -------------------------- 3 | 4 | Those are mentioned here as are not backward compatible. 5 | I want the API to be stable and consistent in the next release and later, 6 | so I do all the big changes now (I would have to do them at some time anyway). 7 | 8 | 2010-04-03 9 | 10 | M2Crypto dependency dropped in favor of the 'ssl' module from the 11 | standard Python library. TLS API has been changed a bit: certificate 12 | verification callback now should expect one argument only (a dictionary 13 | with certificate information) 14 | 15 | Python 2.6 is now _required! 16 | 17 | 2006-08-31 18 | 19 | Interface API for better externalisation of various components. First 20 | introduced to the Client classes -- one may create different classes 21 | to handle different stanza types and namespaces, annotate them with 22 | interfaces implemented (IFeaturesProvider, IPresenceHandlersProvider, 23 | IMessageHandlersProvider or IIqHandlersProvider) and list their 24 | instances in a Client object's interface_providers attribute. 25 | 26 | See the echocomponent.py example to see how to use that. 27 | 28 | 2006-08-26 29 | 30 | Exception handling improvements, part one. 31 | 32 | All PyXMPP-specific exceptions moved to pyxmpp.exceptions module. Some exceptions 33 | have been removed (those which were used where some standard exception, like ValueError, 34 | would be a better choice), the most important omission is StanzaError exception. 35 | 36 | ProtocolError exception with some derivatives added -- these are raised when 37 | an error is detected in a stanza (received) processed. These exceptions 38 | will be handled internally by PyXMPP some day (automatically 39 | or similar XMPP error will be raised). 40 | 41 | 2006-08-26 42 | 43 | CachedPropertyObject class removed and its usage in pyxmpp.jabber.disco 44 | Now all the DiscoItem/DiscoInfo/DiscoItems attributes are regular properties 45 | calling set_*, get_* funtions on every access. 46 | The old cached properties were giving no speed gain and only complicated things. 47 | 48 | 2005-10-14 49 | 50 | Usage of non-unicode strings in PyXMPP API is deprecated. No 51 | no implicit conversion is made where utf-8 string would previously be 52 | accepted (e.g. JID constructor). Sometimes deprecation warning will be 53 | raised when string is used instead of unicode. 54 | 55 | 56 | 2005-03-20 57 | 58 | "common_doc", "common_root", and "common_ns" can no longer be imported 59 | from the "stanza" module. They were moved to the "xmlextra" module. 60 | 61 | 2005-01-20 62 | 63 | Minor change: now "disco_info" and "disco_items" attributes 64 | of pyxmpp.jabber.client and pyxmpp.jabberd.component are initialized 65 | in the constructors of those classes. Those __init__() methods now 66 | accept "disco_name", "disco_category" and "disco_type" arguments 67 | to set up the entity identity. Also register_feature() and 68 | unregister_feature() methods were added for managing the feature 69 | list in .disco_info. 70 | 71 | 2005-01-09 72 | 73 | Continuation of the big API unification. 74 | 75 | All attribute and parameter names "node", relating to XML node have 76 | been renamed to "xmlnode". The most important change is `Stanza.node` renamed 77 | to `Stanza.xmlnode`. 78 | 79 | Roster.items(), Roster.groups(), Roster.items_by_name(), Roster.items_by_group(), 80 | Roser.items_by_jid() renamed to: Roster.get_items(), Roster.get_groups(), 81 | Roster.get_items_by_name(), Roster.get_items_by_group(), Roster.get_item_by_jid() 82 | 83 | Keys of Roster.items_dict are now JIDs, not unicode. 84 | 85 | More .as_xml() methods unified. To reuse most of the code needed two 86 | new virtual classes were added: pyxmpp.objects.StanzaPayloadObject 87 | and pyxmpp.objects.StanzaPayloadWrapperObject. Objects providing .as_xml() 88 | methods may be used directly as argument to Stanza.set_content() and 89 | Stanza.add_content() methods. 90 | 91 | 2005-01-06 92 | 93 | Big API unification started. 94 | 95 | jabber.vcard.VCard and jabber.delay.Delay 96 | classes' method as_xml accepts now "parent" and "doc" arguments in 97 | that order. Argument interpretation is also unified. All other similar 98 | objects will have the add_xml() with the same signature soon. 99 | 100 | Classes in jabber.disco module were modified heavily, so all the 101 | getter are named get_*, all the setters set_* and all the interesting 102 | properties may be accessed as attributes which also adds some caching. 103 | Other classes, if they don't use that get_*/set_* naming, will be 104 | modified in similar way soon. 105 | 106 | 2004-12-31 107 | 108 | There was a design flaw in PyXMPP from the very beginning, which caused 109 | that many unnecessary modules from PyXMPP package (and subpackages, 110 | when they were used) were imported even when only one simple module was 111 | used. That was because pyxmpp imported all the useful names from 112 | submodules. That was a problem (unnecessarily long startup time 113 | sometimes), but it was also quite convenient. 114 | 115 | To resolve the problem those imports were removed from __init__ 116 | modules, so importing a package doesn't cause any other imports now. 117 | To improve backward compatibility and keep the convenience of the old 118 | behavior "all" modules were added to "pyxmpp", "pyxmpp.jabber" and 119 | "pyxmpp.jabber" packages. 120 | 121 | If your application was broken by that change just import "pyxmpp.all" 122 | (and/or "pyxmpp.jabber.all", "pyxmpp.jabberd.all" if you use them) 123 | there -- you need to do it once per application (no need to change 124 | every module). After that everything should work as before. 125 | 126 | If you use only small part of the API and don't want to import unused 127 | modules import names like "JID" or "Stanza" directly from modules that 128 | define them (e.g.: 'import JID from pyxmpp.jid'). 129 | 130 | 2004-12-18 131 | 132 | dnspython (http://www.dnspython.org) is now used for resolving DNS 133 | names. Please note, that because of a conflict between dnspython and 134 | old PyXMPP DNS implementation you must clean up after old PyXMPP 135 | installation and do 'make clean' before building PyXMPP. 136 | 137 | 2004-09-16 138 | 139 | Stanza, Message, Iq and Presence constructors arguments have been 140 | changed again. "typ" and "sid" were really stupid and ugly. 141 | Now Stanza (derived classes) constructor keyword parameters are: 142 | from_jid, to_jid, stanza_type, stanza_id 143 | 144 | I hope this will never change again, at least not after the next 145 | official release. You may update your application code using 146 | "migrate-0_5-0_6.py" script. 147 | 148 | 2004-09-13 149 | 150 | Stanza, Message, Iq and Presence constructors arguments have been 151 | changed so build-ins "type" and "id" are not redefined. So the "type" 152 | argument has been changed to "typ" and the "id" argument to "sid" 153 | (stanza id). 154 | 155 | 2004-09-03 156 | 157 | Stream.data_in and Stream.data_out callbacks removed. Data sent and 158 | received is now logged via 'logging' module to 'pyxmpp.Stream.out' and 159 | 'pyxmpp.Stream.in' loggers. The raw data is available for logging 160 | handlers as the first element of 'args' attribute of the log record 161 | (record.args[0]). 162 | 163 | 2004-08-29 164 | 165 | All debug messages are passed to the standard python 'logging' module. 166 | There is no debug() methods in PyXMPP classes any more. 167 | 168 | 2005-06-02 169 | 170 | Python 2.2 support is gone. Compliant XMPP implementation 171 | requires stringprep and IDN support. Python 2.3 provides both, 172 | Python 2.2 none. Maintaining separete stringprep and IDN 173 | implementations for use with Python 2.2 is IMHO not worth the effort 174 | needed. 175 | 176 | 2005-05-28 177 | 178 | Roster and RosterItems classes rewritten. Now they are not wrapper 179 | around roster XML node any more. Old code was complicated, slow, 180 | and not standard compliant (ingored stringprep profiles). 181 | 182 | Most important changes: 183 | 184 | - no `node` attribute in Roster and RosterItem 185 | - RosterItem constructor doesn't take `roster` argument any more 186 | - `jid`, `ask`, `subscription`, `name` and `groups` are now attributes, 187 | not methods, of `RosterItem` 188 | - `add_group`, `rm_group`, etc. methods of `RosterItem` are gone. Use 189 | list operations on `groups` attribute now. 190 | - inline documentation included 191 | 192 | vi: spell spelllang=en 193 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Makefile 2 | include COPYING TODO ChangeLog CHANGES README README.WIN32 3 | include configure.py build.cfg 4 | include examples/*.py 5 | include tests/*.py 6 | include tests/Makefile 7 | include tests/data/* 8 | include utils/*.py 9 | include doc/*.html 10 | include doc/Makefile 11 | include doc/doc-tstamp 12 | include doc/www/*.html 13 | include doc/www/*.css 14 | include doc/www/api/*.css 15 | include doc/www/api/*.html 16 | include doc/www/api/private/* 17 | include doc/www/api/public/* 18 | include utils/*.py 19 | include auxtools/htmlmerge.py 20 | include ext/Copyright-libxml2 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=1.1.2 2 | SNAPSHOT= 3 | 4 | DESTDIR="/" 5 | 6 | .PHONY: all build test version dist update-doc doc cosmetics TODO.pylint pylint ChangeLog www publish 7 | 8 | all: build test 9 | 10 | build: version 11 | umask 022 ; python setup.py build 12 | -cd examples ; rm -f pyxmpp 2>/dev/null ; ln -s ../build/lib*/pyxmpp . 13 | -cd examples ; chmod a+x *.py 14 | -cd tests ; rm -f pyxmpp 2>/dev/null ; ln -s ../build/lib*/pyxmpp . 15 | -cd tests ; chmod a+x *.py 16 | 17 | test: 18 | $(MAKE) -C tests 19 | 20 | doc: 21 | $(MAKE) -C doc 22 | 23 | update-doc: 24 | $(MAKE) -C doc update-doc 25 | 26 | www: 27 | $(MAKE) -C doc www 28 | 29 | publish: 30 | $(MAKE) -C doc publish 31 | 32 | pylint: TODO.pylint 33 | 34 | TODO.pylint: 35 | ./auxtools/pylint.sh | tee TODO.pylint 36 | 37 | ChangeLog: 38 | test -d .git && make cl-stamp || : 39 | 40 | cl-stamp: .git 41 | git log > ChangeLog 42 | touch cl-stamp 43 | 44 | cosmetics: 45 | ./auxtools/cosmetics.sh 46 | 47 | version: 48 | if test -d ".git" ; then \ 49 | echo "# pylint: disable-msg=W0103,W0131" > pyxmpp/version.py ; \ 50 | echo "version='$(VERSION)+git'" >> pyxmpp/version.py ; \ 51 | fi 52 | 53 | dist: build ChangeLog doc 54 | -rm -f MANIFEST 55 | echo "version='$(VERSION)$(SNAPSHOT)'" > pyxmpp/version.py 56 | python setup.py sdist 57 | 58 | clean: 59 | python setup.py clean --all 60 | -rm -rf build/* 61 | 62 | install: all 63 | umask 022 ; python setup.py install --root $(DESTDIR) 64 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ============================================ 2 | PyXMPP – Python Jabber/XMPP implementation 3 | ============================================ 4 | 5 | Introduction 6 | ------------ 7 | 8 | PyXMPP is a Python XMPP (RFC 3920,3921) implementation, including some of the 9 | well-known extensions. It is based on libxml2 -- fast and fully-featured XML 10 | parser. 11 | 12 | PyXMPP provides most core features of the XMPP protocol and several JSF-defined 13 | extensions. PyXMPP provides building blocks for creating Jabber clients and 14 | components. Developer uses them to setup XMPP streams, handle incoming events 15 | and create outgoing stanzas (XMPP "packets"). 16 | 17 | Please note: this project is becoming obsolete, as now `PyXMPP2`_ 18 | is being developed. PyXMPP2 is a modern rewrite of PyXMPP, using ElementTree 19 | instead of libxml2. 20 | 21 | .. _PyXMPP2: https://github.com/Jajcus/pyxmpp2 22 | 23 | 24 | Features 25 | -------- 26 | 27 | - nearly complete XMPP Core (RFC 3920) protocol for client connections 28 | (includes SASL, TLS and Strinprep). 29 | - mostly complete XMPP IM (RFC 3921) protocol (lacks privacy lists) 30 | - XMPP error objects including translations to and from legacy codes for 31 | backward compatibility (`JEP-0086 32 | `__). 33 | - legacy authentication ("digest" and "plain") (`JEP-0078 34 | `__). 35 | - component protocol (`JEP-0114 `__). 36 | - Service Discovery (`JEP-0030 `__). 37 | - vCards -- both Jabber "vcard-temp" and RFC 2426 38 | - basic parts of the Multi-User Chat protocol (`JEP-0045 39 | `__) 40 | - delayed delivery timestamps (`JEP-0091 `__). 41 | - Data Forms (`JEP-0004 `__). 42 | - In-Band Registration (`JEP-0077 `__). 43 | 44 | Requirements 45 | ------------ 46 | 47 | For PyXMPP you will need: 48 | 49 | - `Python `__. Python 2.6 required. 50 | - `libxml2 `__. Recent release (>=2.6.11) with python 51 | bindings is required. Additionally, all development files for libxml2 (usually in 52 | separate libxml2-devel package) are recommended for compilation of PyXMPP for best performance. 53 | PyXMPP 1.0 was tested with libxml2-2.7.6. 54 | - `dnspython `__. 55 | PyXMPP 1.0 was tested with version 1.6.0. 56 | 57 | Installation 58 | ------------ 59 | 60 | To make sure you have all the requirements listed above or to prepare limited 61 | (without the binary extension) build of PyXMPP you may want to run:: 62 | 63 | python configure.py 64 | 65 | You may manually pick the limited or full build of PyXMPP by editing the build.cfg file. 66 | 67 | To build the package just invoke:: 68 | 69 | python setup.py build 70 | 71 | To install it:: 72 | 73 | python setup.py install 74 | 75 | If you had some older version of PyXMPP it is better to uninstall it (delete 76 | pyxmpp subdirectory os your site-packages directory) before installing this one 77 | or things may not work correctly. 78 | 79 | You may also try:: 80 | 81 | make 82 | 83 | and:: 84 | 85 | make install 86 | 87 | instead. 88 | 89 | Contact 90 | ------- 91 | 92 | PyXMPP was written and is maintained by Jacek Konieczny . 93 | 94 | To report bugs or request features use the `GitHub interface `__. 95 | 96 | For discussion you may join `the PyXMPP mailing list 97 | `__. 98 | 99 | Download 100 | -------- 101 | 102 | Current source code is available via GitHub at: https://github.com/Jajcus/pyxmpp 103 | 104 | Licence 105 | ------- 106 | 107 | PyXMPP is free software, licenced under the GNU LGPL. See the 108 | COPYING file for details. 109 | -------------------------------------------------------------------------------- /README.WIN32: -------------------------------------------------------------------------------- 1 | Notes on how to compile Python extensions on windows without the Microsoft compiler. I assume some basic skills in Windows. Most of this is simply copied from http://sebsauvage.net/python/mingw.html. 2 | 3 | Steps: 4 | 5 | 1. Get and install MinGW gcc 6 | 2. Create libpython2?.a 7 | 3. Get and install required libxml2 libraries and dependencies. 8 | 4. Compile/install the extension with distutils. 9 | 10 | 11 | Get and install MinGW gcc 12 | Get MinGW gcc from http://mingw.org (Minimalist GNU For Windows). The only required packages are MSYS and msysDTK. 13 | 14 | 15 | Create libpython2?.a 16 | To create Python extensions, you need to link against the Python library. Unfortunately, most Python distributions are provided with Python2?.lib, a library in Microsoft Visual C++ format. GCC expects a .a file (libpython2?.a to be precise). Here's how to convert python2?.lib to libpython2?.a: 17 | 18 | 1. Download pexports from http://starship.python.net/crew/kernr/mingw32/. 19 | 2. Extract files and make sure the bin directory is in your path. 20 | 3. Locate Python2?.dll (Found mine under C:\WINNT\system32). 21 | 4. Run: 22 | 23 | pexports python2?.dll > python2?.def 24 | 25 | This will extract all symbols from python2?.dll and write them into python2?.def. 26 | 5. Run: 27 | 28 | dlltool --dllname python2?.dll --def python2?.def --output-lib libpython2?.a 29 | 30 | This will create libpython2?.a (dlltool is part of MinGW utilities). 31 | 6. Copy libpython2?.a to {your install}\python2?\libs\ (in the same directory as python22.lib). This trick should work for all Python versions, including future releases of Python. You can also use this trick to convert other libraries. (Will see about that soon) 32 | 33 | 34 | Get and install required libxml2 libraries and dependencies 35 | Download Win32 binaries for libxml2, zlib and iconv from http://www.zlatkovic.com/libxml.en.html. Unpack the archives preserving directory structure and put them into some directory (e.g. d:\libs). If you exctract the libs in another directory, you will need to supply its locations to distutils (setup.py script). 36 | As libxml2 comes with .lib compiled for MSVC, you must create .a import library just the same as in case of libpython2x.a and put resulting libxml2.a in the same directory as libxml2.lib exists. 37 | 38 | 39 | Compile/install the extension with distutils 40 | Compile it: 41 | python setup.py build -cmingw32 42 | Install it: 43 | python setup.py install --skip-build 44 | Make binary distribution 45 | python setup.py build -cmingw32 bdist_wininst 46 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | general: 2 | - documentation !!! 3 | - better error handling 4 | 5 | xmpp-base: 6 | - jabber:server stream support (including diallback) 7 | 8 | xmpp-im: 9 | - privacy lists 10 | 11 | other: 12 | - jabber:iq:version 13 | - jabberd 2.0 component stream support 14 | - jabber:iq:search 15 | -------------------------------------------------------------------------------- /TODO.pylint: -------------------------------------------------------------------------------- 1 | ************* Module pyxmpp.xmppstringprep 2 | I0011: 18: Locally disabling 'W0402' 3 | ************* Module pyxmpp.cache 4 | W0511:563: TODO: optimize somehow. 5 | ************* Module pyxmpp.streamsasl 6 | I0011: 17: Locally disabling 'W0201' 7 | ************* Module pyxmpp.jid 8 | I0011: 17: Locally disabling 'W0232' 9 | I0011: 17: Locally disabling 'E0201' 10 | ************* Module pyxmpp.xmlextra 11 | I0011: 17: Locally disabling 'C0103' 12 | I0011: 17: Locally disabling 'W0132' 13 | I0011: 17: Locally disabling 'W0611' 14 | ************* Module pyxmpp.streambase 15 | I0011: 17: Locally disabling 'W0201' 16 | ************* Module pyxmpp.clientstream 17 | I0011: 17: Locally disabling 'W0221' 18 | W0511:349: FIXME: that should be the hostname choosen from SRV records found. 19 | ************* Module pyxmpp.all 20 | I0011: 17: Locally disabling 'W0611' 21 | ************* Module pyxmpp.objects 22 | I0011: 17: Locally disabling 'W0232' 23 | I0011: 17: Locally disabling 'E0201' 24 | ************* Module pyxmpp.streamtls 25 | I0011: 17: Locally disabling 'W0201' 26 | ************* Module pyxmpp.version 27 | I0011: 1: Locally disabling 'W0103' 28 | I0011: 1: Locally disabling 'W0131' 29 | ************* Module pyxmpp._xmlextra 30 | I0001: 0: Unable to run raw checkers on built-in module pyxmpp._xmlextra 31 | W0103: 0: Missing required attribute "__revision__" 32 | W0131: 0:remove_ns: Missing docstring 33 | W0131: 0:replace_ns: Missing docstring 34 | W0131: 0:sax_reader_new: Missing docstring 35 | ************* Module pyxmpp.jabber.muc 36 | W0302: 0: Too many lines in module (1020) 37 | W0511:639: TODO: kicks 38 | W0511:691: TODO: ProtocolError 39 | W0511:698: TODO: ProtocolError 40 | ************* Module pyxmpp.jabber.muccore 41 | W0511:203: TODO: since -- requires parsing of Jabber dateTime profile 42 | W0511:508: FIXME: alt,decline,invite,password 43 | W0511:549: FIXME: implement 44 | ************* Module pyxmpp.jabber.all 45 | I0011: 17: Locally disabling 'W0611' 46 | ************* Module pyxmpp.jabber.simple 47 | I0011: 17: Locally disabling 'W0232' 48 | I0011: 17: Locally disabling 'E0201' 49 | ************* Module pyxmpp.jabber.disco 50 | I0011: 17: Locally disabling 'W0201' 51 | ************* Module pyxmpp.jabber.vcard 52 | I0011: 17: Locally disabling 'W0302' 53 | W0511:1326: FIXME: agent field 54 | ************* Module pyxmpp.jabberd.all 55 | I0011: 17: Locally disabling 'W0611' 56 | ************* Module pyxmpp.jabberd.componentstream 57 | I0011: 17: Locally disabling 'W0221' 58 | I0011: 17: Locally disabling 'W0201' 59 | ************* Module pyxmpp.sasl.core 60 | W0511:188: FIXME: use some better RNG (/dev/urandom maybe) 61 | 62 | 63 | Report 64 | ====== 65 | 6811 statements analysed. 66 | 67 | Duplication 68 | ----------- 69 | 70 | now previous difference 71 | ::::::::::::::::::::::::::::::::::::::::::::::::::: 72 | nb duplicated lines 0 0 = 73 | percent duplicated lines 0.000 0.000 = 74 | 75 | 76 | Raw metrics 77 | ----------- 78 | 79 | type number % previous difference 80 | ::::::::::::::::::::::::::::::::::::::::::: 81 | code 7493 47.76 7493 = 82 | docstring 6433 41.01 6433 = 83 | comment 770 4.91 770 = 84 | empty 992 6.32 992 = 85 | 86 | 87 | External dependencies 88 | --------------------- 89 | :: 90 | 91 | stringprep (pyxmpp.xmppstringprep) 92 | random (pyxmpp.streambase,pyxmpp.stanza,pyxmpp.resolver,pyxmpp.sasl.core,pyxmpp.sasl) 93 | logging (pyxmpp.streamsasl,pyxmpp.streambase,pyxmpp.clientstream,pyxmpp.client,pyxmpp.stream,pyxmpp.streamtls,pyxmpp.stanzaprocessor,pyxmpp.jabber.muc,pyxmpp.jabber.clientstream,pyxmpp.jabber.client,pyxmpp.jabber.register,pyxmpp.jabberd.component,pyxmpp.jabberd.componentstream,pyxmpp.sasl.plain,pyxmpp.sasl.core,pyxmpp.sasl.digest_md5) 94 | socket (pyxmpp.streambase,pyxmpp.resolver,pyxmpp.streamtls) 95 | warnings (pyxmpp.jid) 96 | libxml2 (pyxmpp.roster,pyxmpp.presence,pyxmpp.xmlextra,pyxmpp.streambase,pyxmpp.stanza,pyxmpp.message,pyxmpp.objects,pyxmpp.stanzaprocessor,pyxmpp.iq,pyxmpp.error,pyxmpp.jabber.dataforms,pyxmpp.jabber.muccore,pyxmpp.jabber.delay,pyxmpp.jabber.disco,pyxmpp.jabber.vcard,pyxmpp.jabber.register) 97 | base64 (pyxmpp.streamsasl,pyxmpp.jabber.vcard) 98 | weakref (pyxmpp.jid,pyxmpp.jabber.muc) 99 | threading (pyxmpp.cache,pyxmpp.xmlextra,pyxmpp.streambase,pyxmpp.client,pyxmpp.expdict,pyxmpp.stanzaprocessor,pyxmpp.jabberd.component) 100 | M2Crypto 101 | \-X509 (pyxmpp.streamtls) 102 | \-SSL (pyxmpp.streamtls) 103 | | \-cb (pyxmpp.streamtls) 104 | | \-Context (pyxmpp.streamtls) 105 | \-m2 (pyxmpp.streamtls) 106 | dns 107 | \-exception (pyxmpp.resolver) 108 | \-name (pyxmpp.resolver) 109 | \-resolver (pyxmpp.resolver) 110 | encodings 111 | \-idna (pyxmpp.jid,pyxmpp.resolver) 112 | re (pyxmpp.jid,pyxmpp.xmlextra,pyxmpp.resolver,pyxmpp.jabber.vcard,pyxmpp.sasl.digest_md5) 113 | copy (pyxmpp.jabber.dataforms) 114 | os (pyxmpp.streambase) 115 | 116 | 117 | 118 | Modules dependencies graph 119 | -------------------------- 120 | external imports graph has been written to ext_import_graph 121 | internal imports graph has been written to int_import_graph 122 | 123 | 124 | Statistics by type 125 | ------------------ 126 | 127 | type number old number difference %documented %badname 128 | ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 129 | module 45 45 = 97.78 0.00 130 | class 125 125 = 100.00 0.00 131 | method 679 679 = 98.53 0.74 132 | function 47 47 = 93.62 0.00 133 | 134 | 135 | Total errors / warnings 136 | ----------------------- 137 | 138 | type number previous difference 139 | :::::::::::::::::::::::::::::::::::::: 140 | convention 0 0 = 141 | refactor 0 0 = 142 | warning 15 15 = 143 | error 0 3 -3.00 144 | 145 | 146 | % errors / warnings by module 147 | ----------------------------- 148 | 149 | module error warning refactor convention 150 | :::::::::::::::::::::::::::::::::::::::::::::::::::::::: 151 | pyxmpp.jabber.muc 0.00 26.67 0.00 0.00 152 | pyxmpp._xmlextra 0.00 26.67 0.00 0.00 153 | pyxmpp.jabber.muccore 0.00 20.00 0.00 0.00 154 | pyxmpp.sasl.core 0.00 6.67 0.00 0.00 155 | pyxmpp.jabber.vcard 0.00 6.67 0.00 0.00 156 | pyxmpp.clientstream 0.00 6.67 0.00 0.00 157 | pyxmpp.cache 0.00 6.67 0.00 0.00 158 | 159 | 160 | Messages 161 | -------- 162 | 163 | message id occurences 164 | :::::::::::::::::::::: 165 | W0511 10 166 | W0131 3 167 | W0302 1 168 | W0103 1 169 | 170 | 171 | Global evaluation 172 | ----------------- 173 | Your code has been rated at 9.98/10 (previous run: 9.96/10) 174 | 175 | -------------------------------------------------------------------------------- /auxtools/cosmetics.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=`dirname $0` 4 | find . -name "*.py" | xargs -n1 vim -u NONE -s $dir/cosmetics.vim --cmd ":set ts=8" 5 | stty sane 6 | -------------------------------------------------------------------------------- /auxtools/cosmetics.vim: -------------------------------------------------------------------------------- 1 | :set nocompatible 2 | :%s/\s\+$// 3 | :g/^# \(vi\): /d 4 | :set et 5 | :retab! 6 | :$ 7 | :append 8 | # vi: sts=4 et sw=4 9 | . 10 | :update 11 | :q 12 | :q! 13 | -------------------------------------------------------------------------------- /auxtools/htmlmerge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import libxml2 4 | import sys 5 | 6 | 7 | class Merger: 8 | def __init__(self,template_file,content_file): 9 | self.template_doc=libxml2.parseFile(template_file) 10 | self.content_doc=libxml2.parseFile(content_file) 11 | def merge(self): 12 | output_doc=self.template_doc.copyDoc(True) 13 | slot=output_doc.xpathEval('//*[@id="main"]')[0] 14 | content=self.content_doc.xpathEval('//*[@class="document"]/*') 15 | for node in content: 16 | nn=slot.addChild(node.copyNode(True)) 17 | output_doc.getRootElement().reconciliateNs(output_doc) 18 | return output_doc 19 | 20 | m=Merger(sys.argv[1],sys.argv[2]) 21 | out=m.merge() 22 | print out.serialize(format=True) 23 | # vi: sts=4 et sw=4 24 | -------------------------------------------------------------------------------- /auxtools/pylint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | auxdir=`dirname $0` 4 | if [ -z $topdir ]; then 5 | topdir="." 6 | fi 7 | cd $auxdir 8 | auxdir=`pwd` 9 | cd .. 10 | #make >&2 11 | topdir=`pwd` 12 | cd build/lib.* 13 | 14 | if [ -n "$1" ] ; then 15 | pylint --rcfile $auxdir/pylintrc $1 16 | else 17 | pylint --rcfile $auxdir/pylintrc pyxmpp 18 | fi 19 | -------------------------------------------------------------------------------- /auxtools/users: -------------------------------------------------------------------------------- 1 | jajcus Jacek Konieczny 2 | -------------------------------------------------------------------------------- /auxtools/xmimerge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import libxml2 4 | import sys 5 | 6 | 7 | class Merger: 8 | def __init__(self,model_file,auto_file): 9 | self.model_doc=libxml2.parseFile(model_file) 10 | self.auto_doc=libxml2.parseFile(auto_file) 11 | self.xmi_id_map={} 12 | self.auto_xmi_id_map={} 13 | self.old_elements={} 14 | def merge(self): 15 | self.output_doc=libxml2.newDoc("1.0") 16 | new_root=self.output_doc.newChild(None,"XMI",None) 17 | uml_ns=new_root.newNs("org.omg/standards/UML","UML") 18 | root=self.model_doc.getRootElement() 19 | n=root.children 20 | while n: 21 | if n.type!="element": 22 | n=n.next 23 | continue 24 | if n.name=="XMI.content": 25 | new_content=new_root.addChild(n.docCopyNode(self.output_doc,False)) 26 | p=n.get_properties() 27 | while p: 28 | new_content.setProp(p.name,p.getContent()) 29 | p=p.next 30 | self.merge_xmi_content(new_content,n) 31 | else: 32 | new_root.addChild(n.docCopyNode(self.output_doc,True)) 33 | n=n.next 34 | return self.output_doc 35 | 36 | def merge_xmi_content(self,target,old_content): 37 | self.old_elements={} 38 | n=old_content.children 39 | while n: 40 | if n.type!="element": 41 | n=n.next 42 | continue 43 | try: 44 | if n.ns().getContent()=="org.omg/standards/UML" and n.name=="Model": 45 | self.parse_old_model(n) 46 | n=n.next 47 | continue 48 | except libxml2.treeError: 49 | pass 50 | target.addChild(n.docCopyNode(self.output_doc,True)) 51 | n=n.next 52 | auto_content=self.auto_doc.xpathEval("//XMI/XMI.content/*") 53 | for n in auto_content: 54 | if n.ns().getContent()=="org.omg/standards/UML" and n.name=="Model": 55 | new_model=target.addChild(n.docCopyNode(self.output_doc,False)) 56 | p=n.get_properties() 57 | while p: 58 | new_model.setProp(p.name,p.getContent()) 59 | p=p.next 60 | self.merge_uml_model(new_model,n) 61 | else: 62 | target.addChild(n.docCopyNode(self.output_doc,True)) 63 | 64 | def merge_uml_model(self,target,subtree,path=""): 65 | n=subtree.children 66 | while n: 67 | if n.type!="element": 68 | n=n.next 69 | continue 70 | 71 | new_node=target.addChild(n.docCopyNode(self.output_doc,False)) 72 | p=n.get_properties() 73 | while p: 74 | old_val=p.getContent() 75 | if p.name in ("stereotype","parent","child"): 76 | val=self.xmi_id_map.get(old_val) 77 | if not val: 78 | val=self.auto_xmi_id_map.get(old_val) 79 | if not val: 80 | val=old_val 81 | else: 82 | val=old_val 83 | new_node.setProp(p.name,val) 84 | p=p.next 85 | 86 | npath="%s/%s:%s" % (path,n.name,n.prop("name")) 87 | auto_xmi_id=n.prop("xmi.id") 88 | xmi_id=self.xmi_id_map.get(npath) 89 | if xmi_id: 90 | if auto_xmi_id: 91 | self.auto_xmi_id_map[auto_xmi_id]=xmi_id 92 | new_node.setProp("xmi.id",xmi_id) 93 | self.merge_uml_model(new_node,n,npath) 94 | n=n.next 95 | for oe in self.old_elements.get(path,[]): 96 | target.addChild(oe) 97 | 98 | def parse_old_model(self,subtree,path=""): 99 | n=subtree.children 100 | while n: 101 | if n.type!="element": 102 | n=n.next 103 | continue 104 | if (n.ns().getContent()!="org.omg/standards/UML" or 105 | n.name not in ("Stereotype","Class","Operation","Attribute","Package")): 106 | oe=n.docCopyNode(self.output_doc,True) 107 | if not path in self.old_elements: 108 | self.old_elements[path]=[oe] 109 | else: 110 | self.old_elements[path].append(oe) 111 | n=n.next 112 | continue 113 | npath="%s/%s:%s" % (path,n.name,n.prop("name")) 114 | xmi_id=n.prop("xmi.id") 115 | if xmi_id: 116 | self.xmi_id_map[npath]=xmi_id 117 | self.parse_old_model(n,npath) 118 | n=n.next 119 | 120 | m=Merger(sys.argv[1],sys.argv[2]) 121 | out=m.merge() 122 | print out.serialize(format=True) 123 | # vi: sts=4 et sw=4 124 | -------------------------------------------------------------------------------- /build.cfg: -------------------------------------------------------------------------------- 1 | python_only = False 2 | -------------------------------------------------------------------------------- /configure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | 6 | 7 | cfg_python_only = False 8 | 9 | d = os.path.dirname(__file__) 10 | if not d: 11 | d = "." 12 | os.chdir(d) 13 | 14 | print "Checking for python version...", sys.version.replace("\n", " ") 15 | if sys.hexversion < 0x02060000: 16 | print >>sys.stderr, "ERROR: Python 2.6 or newer is required" 17 | sys.exit(1) 18 | 19 | print "Checking for dnspython...", 20 | try: 21 | import dns.resolver 22 | import dns.version 23 | except ImportError: 24 | print "not found" 25 | print >>sys.stderr, "ERROR: You need dnspython from http://www.dnspython.org/" 26 | sys.exit(1) 27 | print "version %s found" % (dns.version.version,) 28 | 29 | print "Checking for libxml2 python bindings...", 30 | try: 31 | import libxml2 32 | except ImportError: 33 | print "not found" 34 | print >>sys.stderr, "ERROR: You need libxml2 python bindings for PyXMPP" 35 | sys.exit(1) 36 | print "found" 37 | 38 | print "Trying to build the binary extension...", 39 | build_cfg = file("build.cfg", "w") 40 | print >>build_cfg, "python_only = False" 41 | build_cfg.close() 42 | try: 43 | os.system("python setup.py clean --all >/dev/null 2>&1") 44 | ret = os.system("python setup.py build_ext >build_test.log 2>&1") 45 | except OSError: 46 | ret = -1 47 | if ret: 48 | print "failed" 49 | print >>sys.stderr, "Warning: Couldn't build the binary extension. Python or libxml2 devel files are missing. Will use python-only implementation." 50 | print >>sys.stderr, "See build_test.log file for failure details." 51 | cfg_python_only = True 52 | else: 53 | print "success" 54 | os.unlink("build_test.log") 55 | cfg_python_only = False 56 | 57 | # Write build.cfg 58 | 59 | build_cfg = file("build.cfg", "w") 60 | print >>build_cfg, "python_only =", cfg_python_only 61 | build_cfg.close() 62 | 63 | print 64 | print "Configuration successfull" 65 | print "You may now build pyxmpp with 'python setup.py build'" 66 | print "and install it with 'python setup.py install'" 67 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | CHANGES.html 2 | ChangeLog.html 3 | index.html 4 | doc-tstamp 5 | www/api/ 6 | www/pyxmpp.html 7 | www/CHANGES.html 8 | www/ChangeLog.html 9 | www/style.css 10 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | EPYDOC=epydoc 2 | 3 | .PHONY: all doc update-doc clean check-epydoc check-docutils \ 4 | pyxmpp.xmi pyxmpp-auto.xmi 5 | 6 | WEBFILES=www/pyxmpp.html www/CHANGES.html www/ChangeLog.html \ 7 | www/style.css 8 | 9 | RST2HTML=rst2html 10 | 11 | all: doc 12 | 13 | www: $(WEBFILES) doc 14 | 15 | doc: doc-tstamp check-epydoc check-docutils 16 | 17 | update-doc: 18 | -rm -f doc-tstamp 19 | $(MAKE) doc 20 | 21 | doc-tstamp: 22 | $(MAKE) -C .. 23 | rm -f pyxmpp 2>/dev/null ; ln -s ../build/lib*/pyxmpp . 24 | $(EPYDOC) --html --show-imports --no-frames \ 25 | -o www/api -n PyXMPP -u http://pyxmpp.jajcus.net/ pyxmpp 26 | touch doc-tstamp 27 | -rm -f pyxmpp 2>/dev/null 28 | 29 | pyxmpp.xmi: 30 | [ -f pyxmpp.xmi ] 31 | [ -f pyxmpp-auto.xmi ] 32 | [ ! -f pyxmpp.xmi.bak ] 33 | mv pyxmpp.xmi pyxmpp.xmi.bak 34 | ../auxtools/xmimerge.py pyxmpp.xmi.bak pyxmpp-auto.xmi > pyxmpp.xmi 35 | 36 | pyxmpp-auto.xmi: 37 | rm -f pyxmpp 2>/dev/null ; ln -s ../build/lib*/pyxmpp . 38 | ../auxtools/code2xmi.py pyxmpp > pyxmpp-auto.xmi 39 | -rm -f pyxmpp 2>/dev/null 40 | 41 | check-epydoc: 42 | @if ! $(EPYDOC) --help >/dev/null ; then \ 43 | echo "*************************************************" ; \ 44 | echo "You need Epydoc to generate PyXMPP documentation." ; \ 45 | echo "You can find it at http://epydoc.sourceforge.net/" ; \ 46 | echo "*************************************************" ; \ 47 | exit 1 ; \ 48 | fi 49 | 50 | check-docutils: 51 | @if ! python -c "import docutils" --help >/dev/null ; then \ 52 | echo "*****************************************************" ; \ 53 | echo "You need docutils to generate PyXMPP documentation. " ; \ 54 | echo "You can find them at http://docutils.sourceforge.net/" ; \ 55 | echo "*****************************************************" ; \ 56 | exit 1 ; \ 57 | fi 58 | 59 | index.html: ../README 60 | $(RST2HTML) --link-stylesheet --rfc-references --initial-header-level=2 ../README > index.html 61 | 62 | CHANGES.html: ../CHANGES 63 | $(RST2HTML) --link-stylesheet --rfc-references --initial-header-level=2 --no-doc-title ../CHANGES > CHANGES.html 64 | 65 | ChangeLog.html: ../ChangeLog 66 | echo -ne '
\n

ChangeLog

\n
' > ChangeLog.html
67 | 	sed -e's/&/\&/;s//\>/' ../ChangeLog >> ChangeLog.html
68 | 	echo -ns '
\n
\n' >> ChangeLog.html 69 | 70 | www/%.html: %.html template.html 71 | ../auxtools/htmlmerge.py template.html $*.html \ 72 | | xmllint --nsclean - | sed -e's/name="\([^"]*\)" id="\1"/name="\1"/g' > $@ 73 | 74 | www/index.html: 75 | 76 | 77 | www/pyxmpp.html: index.html template.html 78 | ../auxtools/htmlmerge.py template.html index.html \ 79 | | xmllint --nsclean - | sed -e's/name="\([^"]*\)" id="\1"/name="\1"/g' > $@ 80 | 81 | clean: 82 | -rm -f index.html CHANGES.html ChangeLog.html 83 | -rm -f pyxmpp-auto.xmi 84 | 85 | .DELETE_ON_ERROR: 86 | -------------------------------------------------------------------------------- /doc/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pyxmpp 6 | 7 | 8 | 9 |
10 |

PyXMPP

11 |

A Python Jabber/XMPP implementation.

12 |
13 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /doc/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyXMPP and PyXMPP 2 5 | 6 | 7 |

PyXMPP and PyXMPP 2

8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /doc/www/style.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | margin: 0; 3 | background: white; 4 | color: black; 5 | font-family: sans-serif; 6 | } 7 | 8 | #title { 9 | text-align: center; 10 | } 11 | 12 | #navblock { 13 | margin: 0; 14 | padding: 0; 15 | width: 22%; 16 | float: left; 17 | } 18 | 19 | #navbar { 20 | margin: 0.2em; 21 | color: black; 22 | background: #d5eeff; 23 | border: thin solid black; 24 | } 25 | 26 | #mainblock { 27 | padding: 0; 28 | margin: 0; 29 | width: 78%; 30 | float: left; 31 | } 32 | 33 | #main { 34 | margin: 0 0.2em; 35 | } 36 | 37 | .linkhead { 38 | margin: 0.2em 0 0 0; 39 | padding: 0.1em; 40 | font-weight: bold; 41 | } 42 | 43 | .link { 44 | margin: 0; 45 | padding: 0.1em; 46 | } 47 | 48 | .current, .current a { 49 | font-style: underline; 50 | font-weight: bold; 51 | } 52 | -------------------------------------------------------------------------------- /examples/c2s_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # -*- coding: utf-8 -*- 3 | 4 | import libxml2 5 | import time 6 | import traceback 7 | import sys 8 | import logging 9 | 10 | from pyxmpp.all import JID,Iq,Presence,Message,StreamError 11 | from pyxmpp.jabber.all import Client 12 | 13 | class Disconnected(Exception): 14 | pass 15 | 16 | class MyClient(Client): 17 | def session_started(self): 18 | self.stream.send(Presence()) 19 | 20 | def idle(self): 21 | print "idle" 22 | Client.idle(self) 23 | if self.session_established: 24 | target=JID("jajcus",s.jid.domain) 25 | self.stream.send(Message(to_jid=target,body=unicode("Teścik","utf-8"))) 26 | 27 | def post_disconnect(self): 28 | print "Disconnected" 29 | raise Disconnected 30 | 31 | logger=logging.getLogger() 32 | logger.addHandler(logging.StreamHandler()) 33 | logger.setLevel(logging.DEBUG) 34 | 35 | libxml2.debugMemory(1) 36 | 37 | print "creating stream..." 38 | s=MyClient(jid=JID("test@localhost/Test"),password=u"123",auth_methods=["sasl:DIGEST-MD5","digest"]) 39 | 40 | print "connecting..." 41 | s.connect() 42 | 43 | print "processing..." 44 | try: 45 | try: 46 | s.loop(1) 47 | finally: 48 | s.disconnect() 49 | except KeyboardInterrupt: 50 | traceback.print_exc(file=sys.stderr) 51 | except (StreamError,Disconnected),e: 52 | raise 53 | 54 | libxml2.cleanupParser() 55 | if libxml2.debugMemory(1) == 0: 56 | print "OK" 57 | else: 58 | print "Memory leak %d bytes" % (libxml2.debugMemory(1)) 59 | libxml2.dumpMemory() 60 | # vi: sts=4 et sw=4 61 | -------------------------------------------------------------------------------- /examples/echobot_old.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # 3 | # This example is a simple "echo" bot. 4 | # 5 | # After connecting to a jabber server it will echo messages, and accept any 6 | # presence subscriptions. This bot has basic Disco support (implemented in 7 | # pyxmpp.jabber.client.Client class) and jabber:iq:vesion. 8 | # 9 | # This version use older, but still supported PyXMPP API 10 | 11 | import sys 12 | import logging 13 | import locale 14 | import codecs 15 | 16 | from pyxmpp.all import JID,Iq,Presence,Message,StreamError 17 | from pyxmpp.jabber.client import JabberClient 18 | 19 | class Client(JabberClient): 20 | """Simple bot (client) example. Uses `pyxmpp.jabber.client.JabberClient` 21 | class as base. That class provides basic stream setup (including 22 | authentication) and Service Discovery server. It also does server address 23 | and port discovery based on the JID provided.""" 24 | 25 | def __init__(self, jid, password): 26 | 27 | # if bare JID is provided add a resource -- it is required 28 | if not jid.resource: 29 | jid=JID(jid.node, jid.domain, "Echobot") 30 | 31 | # setup client with provided connection information 32 | # and identity data 33 | JabberClient.__init__(self, jid, password, 34 | disco_name="PyXMPP example: echo bot", disco_type="bot") 35 | 36 | # register features to be announced via Service Discovery 37 | self.disco_info.add_feature("jabber:iq:version") 38 | 39 | def stream_state_changed(self,state,arg): 40 | """This one is called when the state of stream connecting the component 41 | to a server changes. This will usually be used to let the user 42 | know what is going on.""" 43 | print "*** State changed: %s %r ***" % (state,arg) 44 | 45 | def session_started(self): 46 | """This is called when the IM session is successfully started 47 | (after all the neccessery negotiations, authentication and 48 | authorizasion). 49 | That is the best place to setup various handlers for the stream. 50 | Do not forget about calling the session_started() method of the base 51 | class!""" 52 | JabberClient.session_started(self) 53 | 54 | # set up handlers for supported queries 55 | self.stream.set_iq_get_handler("query","jabber:iq:version",self.get_version) 56 | 57 | # set up handlers for stanzas 58 | self.stream.set_presence_handler(None, self.presence) 59 | self.stream.set_presence_handler("unavailable", self.presence) 60 | self.stream.set_presence_handler("subscribe", self.presence_control) 61 | self.stream.set_presence_handler("subscribed", self.presence_control) 62 | self.stream.set_presence_handler("unsubscribe", self.presence_control) 63 | self.stream.set_presence_handler("unsubscribed", self.presence_control) 64 | 65 | # set up handler for 66 | self.stream.set_message_handler("normal",self.message) 67 | 68 | def get_version(self,iq): 69 | """Handler for jabber:iq:version queries. 70 | 71 | jabber:iq:version queries are not supported directly by PyXMPP, so the 72 | XML node is accessed directly through the libxml2 API. This should be 73 | used very carefully!""" 74 | iq=iq.make_result_response() 75 | q=iq.new_query("jabber:iq:version") 76 | q.newTextChild(q.ns(),"name","Echo component") 77 | q.newTextChild(q.ns(),"version","1.0") 78 | self.stream.send(iq) 79 | return True 80 | 81 | def message(self,stanza): 82 | """Message handler for the component. 83 | 84 | Echoes the message back if its type is not 'error' or 85 | 'headline', also sets own presence status to the message body. Please 86 | note that all message types but 'error' will be passed to the handler 87 | for 'normal' message unless some dedicated handler process them. 88 | 89 | :returns: `True` to indicate, that the stanza should not be processed 90 | any further.""" 91 | subject=stanza.get_subject() 92 | body=stanza.get_body() 93 | t=stanza.get_type() 94 | print u'Message from %s received.' % (unicode(stanza.get_from(),)), 95 | if subject: 96 | print u'Subject: "%s".' % (subject,), 97 | if body: 98 | print u'Body: "%s".' % (body,), 99 | if t: 100 | print u'Type: "%s".' % (t,) 101 | else: 102 | print u'Type: "normal".' % (t,) 103 | if stanza.get_type()=="headline": 104 | # 'headline' messages should never be replied to 105 | return True 106 | if subject: 107 | subject=u"Re: "+subject 108 | m=Message( 109 | to_jid=stanza.get_from(), 110 | from_jid=stanza.get_to(), 111 | stanza_type=stanza.get_type(), 112 | subject=subject, 113 | body=body) 114 | self.stream.send(m) 115 | if body: 116 | p=Presence(status=body) 117 | self.stream.send(p) 118 | return True 119 | 120 | def presence(self,stanza): 121 | """Handle 'available' (without 'type') and 'unavailable' .""" 122 | msg=u"%s has become " % (stanza.get_from()) 123 | t=stanza.get_type() 124 | if t=="unavailable": 125 | msg+=u"unavailable" 126 | else: 127 | msg+=u"available" 128 | 129 | show=stanza.get_show() 130 | if show: 131 | msg+=u"(%s)" % (show,) 132 | 133 | status=stanza.get_status() 134 | if status: 135 | msg+=u": "+status 136 | print msg 137 | 138 | def presence_control(self,stanza): 139 | """Handle subscription control stanzas -- acknowledge 140 | them.""" 141 | msg=unicode(stanza.get_from()) 142 | t=stanza.get_type() 143 | if t=="subscribe": 144 | msg+=u" has requested presence subscription." 145 | elif t=="subscribed": 146 | msg+=u" has accepted our presence subscription request." 147 | elif t=="unsubscribe": 148 | msg+=u" has canceled his subscription of our." 149 | elif t=="unsubscribed": 150 | msg+=u" has canceled our subscription of his presence." 151 | 152 | print msg 153 | p=stanza.make_accept_response() 154 | self.stream.send(p) 155 | return True 156 | 157 | def print_roster_item(self,item): 158 | if item.name: 159 | name=item.name 160 | else: 161 | name=u"" 162 | print (u'%s "%s" subscription=%s groups=%s' 163 | % (unicode(item.jid), name, item.subscription, 164 | u",".join(item.groups)) ) 165 | 166 | def roster_updated(self,item=None): 167 | if not item: 168 | print u"My roster:" 169 | for item in self.roster.get_items(): 170 | self.print_roster_item(item) 171 | return 172 | print u"Roster item updated:" 173 | self.print_roster_item(item) 174 | 175 | # XMPP protocol is Unicode-based to properly display data received 176 | # _must_ convert it to local encoding or UnicodeException may be raised 177 | locale.setlocale(locale.LC_CTYPE,"") 178 | encoding=locale.getlocale()[1] 179 | if not encoding: 180 | encoding="us-ascii" 181 | sys.stdout=codecs.getwriter(encoding)(sys.stdout,errors="replace") 182 | sys.stderr=codecs.getwriter(encoding)(sys.stderr,errors="replace") 183 | 184 | 185 | # PyXMPP uses `logging` module for its debug output 186 | # applications should set it up as needed 187 | logger=logging.getLogger() 188 | logger.addHandler(logging.StreamHandler()) 189 | logger.setLevel(logging.INFO) # change to DEBUG for higher verbosity 190 | 191 | if len(sys.argv)<3: 192 | print u"Usage:" 193 | print "\t%s JID password" % (sys.argv[0],) 194 | print "example:" 195 | print "\t%s test@localhost verysecret" % (sys.argv[0],) 196 | sys.exit(1) 197 | 198 | print u"creating client..." 199 | c=Client(JID(sys.argv[1]),sys.argv[2]) 200 | 201 | print u"connecting..." 202 | c.connect() 203 | 204 | print u"looping..." 205 | try: 206 | # Component class provides basic "main loop" for the applitation 207 | # Though, most applications would need to have their own loop and call 208 | # component.stream.loop_iter() from it whenever an event on 209 | # component.stream.fileno() occurs. 210 | c.loop(1) 211 | except KeyboardInterrupt: 212 | print u"disconnecting..." 213 | c.disconnect() 214 | 215 | print u"exiting..." 216 | # vi: sts=4 et sw=4 217 | -------------------------------------------------------------------------------- /examples/getcert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # 3 | # This example fetches server certificate 4 | # 5 | 6 | import sys 7 | import logging 8 | import locale 9 | import codecs 10 | import ssl 11 | 12 | from pyxmpp.jabber.client import JabberClient 13 | from pyxmpp.streamtls import TLSSettings 14 | from pyxmpp.jid import JID 15 | 16 | class Client(JabberClient): 17 | """Simple client which extracts server certificate.""" 18 | def __init__(self, server_jid): 19 | jid = JID("dummy", server_jid.domain, "GetCert") 20 | 21 | tls_settings = TLSSettings(require = True, verify_peer = False) 22 | 23 | # setup client with provided connection information 24 | # and identity data 25 | JabberClient.__init__(self, jid, "", 26 | disco_name="PyXMPP example: getcert.py", disco_type="bot", 27 | tls_settings = tls_settings) 28 | 29 | def stream_state_changed(self, state, arg): 30 | """This one is called when the state of stream connecting the component 31 | to a server changes. This will usually be used to let the user 32 | know what is going on.""" 33 | if state == 'fully connected': 34 | self.disconnect() 35 | if state != 'tls connected': 36 | return 37 | cert = self.stream.tls.getpeercert(True) 38 | pem_cert = ssl.DER_cert_to_PEM_cert(cert).strip() 39 | if pem_cert[-len(ssl.PEM_FOOTER)-1:len(ssl.PEM_FOOTER)] != '\n': 40 | # Python 2.6.4 but workaround 41 | pem_cert = pem_cert[:-len(ssl.PEM_FOOTER)] + "\n" + ssl.PEM_FOOTER 42 | print pem_cert 43 | 44 | # XMPP protocol is Unicode-based to properly display data received 45 | # _must_ convert it to local encoding or UnicodeException may be raised 46 | locale.setlocale(locale.LC_CTYPE, "") 47 | encoding = locale.getlocale()[1] 48 | if not encoding: 49 | encoding = "us-ascii" 50 | sys.stdout = codecs.getwriter(encoding)(sys.stdout, errors = "replace") 51 | sys.stderr = codecs.getwriter(encoding)(sys.stderr, errors = "replace") 52 | 53 | # PyXMPP uses `logging` module for its debug output 54 | # applications should set it up as needed 55 | logger = logging.getLogger() 56 | logger.addHandler(logging.StreamHandler()) 57 | logger.setLevel(logging.ERROR) # change to DEBUG for higher verbosity 58 | 59 | if len(sys.argv) != 2: 60 | print u"Usage:" 61 | print "\t%s domain" % (sys.argv[0],) 62 | print "example:" 63 | print "\t%s jabber.org" % (sys.argv[0],) 64 | sys.exit(1) 65 | 66 | c=Client(JID(sys.argv[1])) 67 | c.connect() 68 | try: 69 | c.loop(1) 70 | except KeyboardInterrupt: 71 | c.disconnect() 72 | 73 | # vi: sts=4 et sw=4 74 | -------------------------------------------------------------------------------- /examples/send_message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # 3 | # A simple message-sending script 4 | 5 | import sys 6 | 7 | from pyxmpp.jid import JID 8 | from pyxmpp.jabber.simple import send_message 9 | 10 | if len(sys.argv)!=6: 11 | print u"Usage:" 12 | print "\t%s my_jid my_password recipient_jid subject body" % (sys.argv[0],) 13 | print "example:" 14 | print "\t%s test@localhost verysecret test1@localhost Test 'this is test'" % (sys.argv[0],) 15 | sys.exit(1) 16 | 17 | jid,password,recpt,subject,body=sys.argv[1:] 18 | jid=JID(jid) 19 | if not jid.resource: 20 | jid=JID(jid.node,jid.domain,"send_message") 21 | recpt=JID(recpt) 22 | send_message(jid,password,recpt,body,subject) 23 | # vi: sts=4 et sw=4 24 | -------------------------------------------------------------------------------- /examples/server_c2s.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | 3 | import libxml2 4 | import time 5 | import sys 6 | import traceback 7 | import socket 8 | import logging 9 | 10 | from pyxmpp.all import JID,Iq,Presence,Message,StreamError 11 | from pyxmpp.jabber.all import LegacyClientStream 12 | 13 | accounts={ 14 | u'test': '123', 15 | }; 16 | 17 | class Stream(LegacyClientStream): 18 | def state_change(self,state,arg): 19 | print "*** State changed: %s %r ***" % (state,arg) 20 | if state=="authorized": 21 | self.disconnect_time=time.time()+60 22 | if not self.version: 23 | self.welcome() 24 | return 25 | self.set_iq_set_handler("session","urn:ietf:params:xml:ns:xmpp-session", 26 | self.set_session) 27 | 28 | def set_session(self,stanza): 29 | fr=stanza.get_from() 30 | if fr and fr!=self.peer: 31 | iq=stanza.make_error_response("forbidden") 32 | self.send(iq) 33 | else: 34 | iq=stanza.make_result_response() 35 | iq.set_to(None) 36 | self.send(iq) 37 | self.welcome() 38 | iq.free() 39 | 40 | def welcome(self): 41 | m=Message(type="chat",to=self.peer, 42 | body="You have authenticated with: %r" % (self.auth_method_used)) 43 | self.send(m) 44 | m=Message(type="chat",to=self.peer,body="You will be disconnected in 1 minute.") 45 | self.send(m) 46 | m=Message(type="chat",to=self.peer,body="Thank you for testing.") 47 | self.send(m) 48 | self.set_message_handler('chat',self.echo_message) 49 | self.set_message_handler('normal',self.echo_message) 50 | 51 | def echo_message(self,message): 52 | typ=message.get_type() 53 | body=message.get_body() 54 | if not body: 55 | return 56 | body=u"ECHO: %s" % (body,) 57 | subject=message.get_subject() 58 | if subject: 59 | subject=u"Re: %s" % (subject,) 60 | m=Message(type=typ,to=self.peer,body=body,subject=subject) 61 | self.send(m) 62 | 63 | def idle(self): 64 | ClientStream.idle(self) 65 | if not self.peer_authenticated: 66 | return 67 | if time.time()>=self.disconnect_time: 68 | m=Message(type="chat",to=self.peer,body="Bye.") 69 | self.send(m) 70 | self.disconnect() 71 | 72 | # def get_realms(self): 73 | # return None 74 | 75 | def get_password(self,username,realm=None,acceptable_formats=("plain",)): 76 | if "plain" in acceptable_formats and accounts.has_key(username): 77 | return accounts[username],"plain" 78 | return None,None 79 | 80 | logger=logging.getLogger() 81 | logger.addHandler(logging.StreamHandler()) 82 | logger.setLevel(logging.DEBUG) 83 | 84 | print "creating socket..." 85 | sock=socket.socket() 86 | sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 87 | sock.bind(("127.0.0.1",5222)) 88 | sock.listen(1) 89 | 90 | print "creating stream..." 91 | s=Stream(JID("localhost"),auth_methods=("sasl:DIGEST-MD5","plain","digest")) 92 | 93 | while 1: 94 | print "accepting..." 95 | s.accept(sock) 96 | 97 | print "processing..." 98 | try: 99 | try: 100 | s.loop(1) 101 | finally: 102 | print "closing..." 103 | s.close() 104 | except StreamError: 105 | traceback.print_exc(file=sys.stderr) 106 | continue 107 | # vi: sts=4 et sw=4 108 | -------------------------------------------------------------------------------- /ext/Copyright-libxml2: -------------------------------------------------------------------------------- 1 | Below is the original copyright notice from libxml2. Files in this directory 2 | are highly-modified parts of libxml2 library and its python bindings. 3 | 4 | 5 | Except where otherwise noted in the source code (trio files, hash.c and list.c) 6 | covered by a similar licence but with different Copyright notices: 7 | 8 | Copyright (C) 1998-2002 Daniel Veillard. All Rights Reserved. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is fur- 15 | nished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- 22 | NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- 25 | NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | Except as contained in this notice, the name of Daniel Veillard shall not 28 | be used in advertising or otherwise to promote the sale, use or other deal- 29 | ings in this Software without prior written authorization from him. 30 | 31 | -------------------------------------------------------------------------------- /pyxmpp/.gitignore: -------------------------------------------------------------------------------- 1 | version.py 2 | -------------------------------------------------------------------------------- /pyxmpp/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """ 19 | PyXMPP - Jabber/XMPP protocol implementation 20 | ============================================ 21 | 22 | Conventions 23 | ----------- 24 | 25 | PyXMPP is object-oriented, most of its fetures are implemented via classes, 26 | defined in various pyxmpp modules. The API is very asynchronous -- often 27 | requested objects are not returned immediately, but instead a callback is 28 | called when the object is available or an event occurs. 29 | 30 | As python is not a strongly-typed language so the parameter and attribute types 31 | shown in this documentation are not enforced, but those types are expected by 32 | the package and others may simply not work or stop working in future releases 33 | of PyXMPP. 34 | 35 | Module hierarchy 36 | ................ 37 | 38 | Base XMPP features (`RFC 3920 `__, `RFC 39 | 3921 `__) are implemented in direct 40 | submodules of `pyxmpp` package. Most `JSF `__ defined 41 | extensions are defined in `pyxmpp.jabber` package and modules for server 42 | components are placed in `pyxmpp.jabberd`. 43 | 44 | For convenience most important names (classes for application use) may be 45 | imported into `pyxmpp`, `pyxmpp.jabber` or `pyxmpp.jabberd` packages. To do 46 | that `pyxmpp.all`, `pyxmpp.jabber.all` or `pyxmpp.jabberd.all` must be 47 | imported. One doesn't have to remember any other module name then. 48 | 49 | Constructors 50 | ............ 51 | 52 | Most of PyXMPP object constructors are polymorphic. That means they accept 53 | different types and number of arguments to create object from various input. 54 | Usually the first argument may be an XML node to parse/wrap into the object 55 | or parameters needed to create a new object from scratch. E.g. 56 | `pyxmpp.stanza.Stanza` constructor accepts single `libxml2.xmlNode` argument 57 | with XML stanza or set of keyword arguments (from_jid, to_jid, stanza_type, 58 | etc.) to create such XML stanza. Most of the constructors will also accept 59 | instance of their own class to create a copy of it. 60 | 61 | Common methods 62 | .............. 63 | 64 | Most objects describing elements of the XMPP protocol or its extensions have 65 | method as_xml() providing their XML representations. 66 | """ 67 | 68 | 69 | __docformat__="restructuredtext en" 70 | 71 | # vi: sts=4 et sw=4 72 | -------------------------------------------------------------------------------- /pyxmpp/all.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | # pylint: disable-msg=W0611 18 | 19 | """Convenience module containing most important objects from pyxmpp package. 20 | 21 | Suggested usage:: 22 | import pyxmpp.all 23 | 24 | (imports all important names into pyxmpp namespace)""" 25 | 26 | """PyXMPP - Jabber/XMPP protocol implementation""" 27 | 28 | __docformat__="restructuredtext en" 29 | 30 | import pyxmpp 31 | 32 | from pyxmpp.stream import Stream 33 | from pyxmpp.streambase import StreamError,FatalStreamError,StreamParseError 34 | from pyxmpp.streamtls import StreamEncryptionRequired,tls_available,TLSSettings 35 | from pyxmpp.clientstream import ClientStream,ClientStreamError 36 | from pyxmpp.client import Client,ClientError 37 | from pyxmpp.iq import Iq 38 | from pyxmpp.presence import Presence 39 | from pyxmpp.message import Message 40 | from pyxmpp.jid import JID,JIDError 41 | from pyxmpp.roster import Roster,RosterItem 42 | from pyxmpp.exceptions import * 43 | 44 | for name in dir(): 45 | if not name.startswith("_") and name != "pyxmpp": 46 | setattr(pyxmpp,name,globals()[name]) 47 | 48 | # vi: sts=4 et sw=4 49 | -------------------------------------------------------------------------------- /pyxmpp/exceptions.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """PyXMPP exceptions. 19 | 20 | This module defines all exceptions raised by PyXMPP. 21 | """ 22 | 23 | __docformat__="restructuredtext en" 24 | 25 | import logging 26 | 27 | 28 | class Error(StandardError): 29 | """Base class for all PyXMPP exceptions.""" 30 | pass 31 | 32 | class JIDError(Error, ValueError): 33 | "Exception raised when invalid JID is used" 34 | pass 35 | 36 | class StreamError(Error): 37 | """Base class for all stream errors.""" 38 | pass 39 | 40 | class StreamEncryptionRequired(StreamError): 41 | """Exception raised when stream encryption is requested, but not used.""" 42 | pass 43 | 44 | class HostMismatch(StreamError): 45 | """Exception raised when the connected host name is other then requested.""" 46 | pass 47 | 48 | class FatalStreamError(StreamError): 49 | """Base class for all fatal Stream exceptions. 50 | 51 | When `FatalStreamError` is raised the stream is no longer usable.""" 52 | pass 53 | 54 | class StreamParseError(FatalStreamError): 55 | """Raised when invalid XML is received in an XMPP stream.""" 56 | pass 57 | 58 | class DNSError(FatalStreamError): 59 | """Raised when no host name could be resolved for the target.""" 60 | pass 61 | 62 | class UnexpectedCNAMEError(DNSError): 63 | """Raised when CNAME record was found when A or AAAA was expected.""" 64 | pass 65 | 66 | class StreamAuthenticationError(FatalStreamError): 67 | """Raised when stream authentication fails.""" 68 | pass 69 | 70 | class TLSNegotiationFailed(FatalStreamError): 71 | """Raised when stream TLS negotiation fails.""" 72 | pass 73 | 74 | class TLSError(FatalStreamError): 75 | """Raised on TLS error during stream processing.""" 76 | pass 77 | 78 | class TLSNegotiatedButNotAvailableError(TLSError): 79 | """Raised on TLS error during stream processing.""" 80 | pass 81 | 82 | class SASLNotAvailable(StreamAuthenticationError): 83 | """Raised when SASL authentication is requested, but not available.""" 84 | pass 85 | 86 | class SASLMechanismNotAvailable(StreamAuthenticationError): 87 | """Raised when none of SASL authentication mechanisms requested is 88 | available.""" 89 | pass 90 | 91 | class SASLAuthenticationFailed(StreamAuthenticationError): 92 | """Raised when stream SASL authentication fails.""" 93 | pass 94 | 95 | class StringprepError(Error): 96 | """Exception raised when string preparation results in error.""" 97 | pass 98 | 99 | class ClientError(Error): 100 | """Raised on a client error.""" 101 | pass 102 | 103 | class FatalClientError(ClientError): 104 | """Raised on a fatal client error.""" 105 | pass 106 | 107 | class ClientStreamError(StreamError): 108 | """Raised on a client stream error.""" 109 | pass 110 | 111 | class FatalClientStreamError(FatalStreamError): 112 | """Raised on a fatal client stream error.""" 113 | pass 114 | 115 | class LegacyAuthenticationError(ClientStreamError): 116 | """Raised on a legacy authentication error.""" 117 | pass 118 | 119 | class RegistrationError(ClientStreamError): 120 | """Raised on a in-band registration error.""" 121 | pass 122 | 123 | class ComponentStreamError(StreamError): 124 | """Raised on a component error.""" 125 | pass 126 | 127 | class FatalComponentStreamError(ComponentStreamError,FatalStreamError): 128 | """Raised on a fatal component error.""" 129 | pass 130 | 131 | ######################## 132 | # Protocol Errors 133 | 134 | class ProtocolError(Error): 135 | """Raised when there is something wrong with a stanza processed. 136 | 137 | When not processed earlier by an application, the exception will be catched 138 | by the stanza dispatcher to return XMPP error to the stanza sender, when 139 | allowed. 140 | 141 | ProtocolErrors handled internally by PyXMPP will be logged via the logging 142 | interface. Errors reported to the sender will be logged using 143 | "pyxmpp.ProtocolError.reported" channel and the ignored errors using 144 | "pyxmpp.ProtocolError.ignored" channel. Both with the "debug" level. 145 | 146 | :Ivariables: 147 | - `xmpp_name` -- XMPP error name which should be reported. 148 | - `message` -- the error message.""" 149 | 150 | logger_reported = logging.getLogger("pyxmpp.ProtocolError.reported") 151 | logger_ignored = logging.getLogger("pyxmpp.ProtocolError.ignored") 152 | 153 | def __init__(self, xmpp_name, message): 154 | self.args = (xmpp_name, message) 155 | @property 156 | def xmpp_name(self): 157 | return self.args[0] 158 | @property 159 | def message(self): 160 | return self.args[1] 161 | def log_reported(self): 162 | self.logger_reported.debug(u"Protocol error detected: %s", self.message) 163 | def log_ignored(self): 164 | self.logger_ignored.debug(u"Protocol error detected: %s", self.message) 165 | def __unicode__(self): 166 | return str(self.args[1]) 167 | def __repr__(self): 168 | return "" % (self.xmpp_name, self.message) 169 | 170 | class BadRequestProtocolError(ProtocolError): 171 | """Raised when invalid stanza is processed and 'bad-request' error should be reported.""" 172 | def __init__(self, message): 173 | ProtocolError.__init__(self, "bad-request", message) 174 | 175 | class JIDMalformedProtocolError(ProtocolError, JIDError): 176 | """Raised when invalid JID is encountered.""" 177 | def __init__(self, message): 178 | ProtocolError.__init__(self, "jid-malformed", message) 179 | 180 | class FeatureNotImplementedProtocolError(ProtocolError): 181 | """Raised when stanza requests a feature which is not (yet) implemented.""" 182 | def __init__(self, message): 183 | ProtocolError.__init__(self, "feature-not-implemented", message) 184 | 185 | # vi: sts=4 et sw=4 186 | -------------------------------------------------------------------------------- /pyxmpp/expdict.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Dictionary with item expiration.""" 19 | 20 | __docformat__="restructuredtext en" 21 | 22 | import time 23 | import threading 24 | 25 | __all__ = ['ExpiringDictionary'] 26 | 27 | sentinel = object() 28 | 29 | class ExpiringDictionary(dict): 30 | """An extension to standard Python dictionary objects which implements item 31 | expiration. 32 | 33 | Each item in ExpiringDictionary has its expiration time assigned, after 34 | which the item is removed from the mapping. 35 | 36 | :Ivariables: 37 | - `_timeouts`: a dictionary with timeout values and timeout callback for 38 | stored objects. 39 | - `_default_timeout`: the default timeout value (in seconds from now). 40 | - `_lock`: access synchronization lock. 41 | :Types: 42 | - `_timeouts`: `dict` 43 | - `_default_timeout`: `int` 44 | - `_lock`: `threading.RLock`""" 45 | 46 | __slots__=['_timeouts','_default_timeout','_lock'] 47 | 48 | def __init__(self,default_timeout=300): 49 | """Initialize an `ExpiringDictionary` object. 50 | 51 | :Parameters: 52 | - `default_timeout`: default timeout value for stored objects. 53 | :Types: 54 | - `default_timeout`: `int`""" 55 | dict.__init__(self) 56 | self._timeouts={} 57 | self._default_timeout=default_timeout 58 | self._lock=threading.RLock() 59 | 60 | def __delitem__(self,key): 61 | self._lock.acquire() 62 | try: 63 | del self._timeouts[key] 64 | return dict.__delitem__(self,key) 65 | finally: 66 | self._lock.release() 67 | 68 | def __getitem__(self,key): 69 | self._lock.acquire() 70 | try: 71 | self._expire_item(key) 72 | return dict.__getitem__(self,key) 73 | finally: 74 | self._lock.release() 75 | 76 | def pop(self,key,default=sentinel): 77 | self._lock.acquire() 78 | try: 79 | self._expire_item(key) 80 | del self._timeouts[key] 81 | if default is not sentinel: 82 | return dict.pop(self,key,default) 83 | else: 84 | return dict.pop(self,key) 85 | finally: 86 | self._lock.release() 87 | 88 | def __setitem__(self,key,value): 89 | return self.set_item(key,value) 90 | 91 | def set_item(self,key,value,timeout=None,timeout_callback=None): 92 | """Set item of the dictionary. 93 | 94 | :Parameters: 95 | - `key`: the key. 96 | - `value`: the object to store. 97 | - `timeout`: timeout value for the object (in seconds from now). 98 | - `timeout_callback`: function to be called when the item expires. 99 | The callback should accept none, one (the key) or two (the key 100 | and the value) arguments. 101 | :Types: 102 | - `key`: any hashable value 103 | - `value`: any python object 104 | - `timeout`: `int` 105 | - `timeout_callback`: callable""" 106 | self._lock.acquire() 107 | try: 108 | if not timeout: 109 | timeout=self._default_timeout 110 | self._timeouts[key]=(time.time()+timeout,timeout_callback) 111 | return dict.__setitem__(self,key,value) 112 | finally: 113 | self._lock.release() 114 | 115 | def expire(self): 116 | """Do the expiration of dictionary items. 117 | 118 | Remove items that expired by now from the dictionary.""" 119 | self._lock.acquire() 120 | try: 121 | for k in self._timeouts.keys(): 122 | self._expire_item(k) 123 | finally: 124 | self._lock.release() 125 | 126 | def _expire_item(self,key): 127 | """Do the expiration of a dictionary item. 128 | 129 | Remove the item if it has expired by now. 130 | 131 | :Parameters: 132 | - `key`: key to the object. 133 | :Types: 134 | - `key`: any hashable value""" 135 | (timeout,callback)=self._timeouts[key] 136 | if timeout<=time.time(): 137 | item = dict.pop(self, key) 138 | del self._timeouts[key] 139 | if callback: 140 | try: 141 | callback(key,item) 142 | except TypeError: 143 | try: 144 | callback(key) 145 | except TypeError: 146 | callback() 147 | 148 | # vi: sts=4 et sw=4 149 | -------------------------------------------------------------------------------- /pyxmpp/interface.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2006 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Interface API. 19 | 20 | If zope.interface is available this module will be its equivalent, otherwise 21 | minimum interface API (partially compatible with zope.interface) will be 22 | defined here. 23 | 24 | When full ZopeInterfaces API is needed impoer zope.interface instead of this module.""" 25 | 26 | 27 | try: 28 | from zope.interface import Interface, Attribute, providedBy, implementedBy, implements 29 | except ImportError: 30 | from pyxmpp.interface_micro_impl import Interface, Attribute, providedBy, implementedBy, implements 31 | 32 | 33 | __all__ = ("Interface", "Attribute", "providedBy", "implementedBy", "implements") 34 | 35 | # vi: sts=4 et sw=4 36 | -------------------------------------------------------------------------------- /pyxmpp/interface_micro_impl.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2006 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Interface API, minimal implementation. 19 | 20 | This is minimal Zope Interfaces API implementation, as required by PyXMPP, not add another dependencies. 21 | 22 | If zope.interface package is available it will be used instead of this one. Never import this module directly.""" 23 | 24 | __docformat__="restructuredtext en" 25 | 26 | import sys 27 | from types import FunctionType 28 | 29 | def classImplements(cls, *interfaces): 30 | if not isinstance(cls, classobj): 31 | raise TypeError, "%r is not a class" 32 | for interface in interfaces: 33 | if not isinstance(interface, InterfaceClass): 34 | raise TypeError, "Only interfaces may be implemented" 35 | cls.__implemented__ = tuple(interfaces) 36 | 37 | def implements(*interfaces): 38 | for interface in interfaces: 39 | if not isinstance(interface, InterfaceClass): 40 | raise TypeError, "Only interfaces may be implemented" 41 | 42 | frame = sys._getframe(1) 43 | locals = frame.f_locals 44 | 45 | if (locals is frame.f_globals) or ('__module__' not in locals): 46 | raise TypeError, "implements() may only be used in a class definition" 47 | 48 | if "__implemented__" in locals: 49 | raise TypeError, "implements() may be used only once" 50 | 51 | locals["__implemented__"] = tuple(interfaces) 52 | 53 | def _whole_tree(cls): 54 | yield cls 55 | for base in cls.__bases__: 56 | for b in _whole_tree(base): 57 | yield b 58 | 59 | def implementedBy(cls): 60 | try: 61 | for interface in cls.__implemented__: 62 | for c in _whole_tree(interface): 63 | yield c 64 | except AttributeError: 65 | pass 66 | for base in cls.__bases__: 67 | for interface in implementedBy(base): 68 | yield interface 69 | 70 | def providedBy(ob): 71 | try: 72 | for interface in ob.__provides__: 73 | yield interface 74 | except AttributeError: 75 | try: 76 | for interface in implementedBy(ob.__class__): 77 | yield interface 78 | except AttributeError: 79 | return 80 | 81 | class InterfaceClass(object): 82 | def __init__(self, name, bases = (), attrs = None, __doc__ = None, __module__ = None): 83 | if __module__ is None: 84 | if (attrs is not None and ('__module__' in attrs) and isinstance(attrs['__module__'], str)): 85 | __module__ = attrs['__module__'] 86 | del attrs['__module__'] 87 | else: 88 | __module__ = sys._getframe(1).f_globals['__name__'] 89 | if __doc__ is not None: 90 | self.__doc__ = __doc__ 91 | if attrs is not None and "__doc__" in attrs: 92 | del attrs["__doc__"] 93 | self.__module__ = __module__ 94 | for base in bases: 95 | if not isinstance(base, InterfaceClass): 96 | raise TypeError, 'Interface bases must be Interfaces' 97 | if attrs is not None: 98 | for aname, attr in attrs.items(): 99 | if not isinstance(attr, Attribute) and type(attr) is not FunctionType: 100 | raise TypeError, 'Interface attributes must be Attributes o functions (%r found in %s)' % (attr, aname) 101 | self.__bases__ = bases 102 | self.__attrs = attrs 103 | self.__name__ = name 104 | self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) 105 | 106 | def providedBy(self, ob): 107 | """Is the interface implemented by an object""" 108 | if self in providedBy(ob): 109 | return True 110 | return False 111 | 112 | def implementedBy(self, cls): 113 | """Do instances of the given class implement the interface?""" 114 | return self in implementedBy(cls) 115 | 116 | def __repr__(self): 117 | name = self.__name__ 118 | module = self.__module__ 119 | if module and module != "__main__": 120 | name = "%s.%s" % (module, name) 121 | return "<%s %s>" % (self.__class__.__name__, name) 122 | 123 | class Attribute(object): 124 | def __init__(self, doc): 125 | self.__doc__ = doc 126 | 127 | Interface = InterfaceClass("Interface", __module__ = "pyxmpp.inteface_micro_impl") 128 | 129 | # vi: sts=4 et sw=4 130 | -------------------------------------------------------------------------------- /pyxmpp/interfaces.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Interfaces for flexible API extensions.""" 19 | 20 | __docformat__ = "restructuredtext en" 21 | 22 | from pyxmpp.interface import Interface, Attribute 23 | 24 | class IPyXMPPHelper(Interface): 25 | """Base for all interfaces used as PyXMPP helpers.""" 26 | 27 | class IPresenceHandlersProvider(IPyXMPPHelper): 28 | def get_presence_handlers(): 29 | """Returns iterable over (presence_type, handler[, namespace[, priority]]) tuples. 30 | 31 | The tuples will be used as arguments for `Stream.set_presence_handler`.""" 32 | 33 | class IMessageHandlersProvider(IPyXMPPHelper): 34 | def get_message_handlers(): 35 | """Returns iterable over (message_type, handler[, namespace[, priority]]) tuples. 36 | 37 | The tuples will be used as arguments for `Stream.set_message_handler`.""" 38 | 39 | class IIqHandlersProvider(IPyXMPPHelper): 40 | def get_iq_get_handlers(): 41 | """Returns iterable over (element_name, namespace) tuples. 42 | 43 | The tuples will be used as arguments for `Stream.set_iq_get_handler`.""" 44 | def get_iq_set_handlers(): 45 | """Returns iterable over (element_name, namespace) tuples. 46 | 47 | The tuples will be used as arguments for `Stream.set_iq_set_handler`.""" 48 | 49 | class IStanzaHandlersProvider(IPresenceHandlersProvider, IMessageHandlersProvider, IIqHandlersProvider): 50 | pass 51 | 52 | class IFeaturesProvider(IPyXMPPHelper): 53 | def get_features(): 54 | """Return iterable of namespaces (features) supported, for disco#info 55 | query response.""" 56 | 57 | 58 | __all__ = [ name for name in dir() if name.startswith("I") and name != "Interface" ] 59 | 60 | # vi: sts=4 et sw=4 61 | -------------------------------------------------------------------------------- /pyxmpp/iq.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Iq XMPP stanza handling 19 | 20 | Normative reference: 21 | - `RFC 3920 `__ 22 | """ 23 | 24 | __docformat__="restructuredtext en" 25 | 26 | import libxml2 27 | 28 | from pyxmpp.xmlextra import get_node_ns_uri 29 | from pyxmpp.stanza import Stanza, gen_id 30 | 31 | class Iq(Stanza): 32 | """Wraper object for stanzas.""" 33 | stanza_type="iq" 34 | def __init__(self, xmlnode = None, from_jid = None, to_jid = None, stanza_type = None, 35 | stanza_id = None, error = None, error_cond=None, stream = None): 36 | """Initialize an `Iq` object. 37 | 38 | :Parameters: 39 | - `xmlnode`: XML node to_jid be wrapped into the `Iq` object 40 | or other Iq object to be copied. If not given then new 41 | presence stanza is created using following parameters. 42 | - `from_jid`: sender JID. 43 | - `to_jid`: recipient JID. 44 | - `stanza_type`: staza type: one of: "get", "set", "result" or "error". 45 | - `stanza_id`: stanza id -- value of stanza's "id" attribute. If not 46 | given, then unique for the session value is generated. 47 | - `error_cond`: error condition name. Ignored if `stanza_type` is not "error". 48 | :Types: 49 | - `xmlnode`: `unicode` or `libxml2.xmlNode` or `Iq` 50 | - `from_jid`: `JID` 51 | - `to_jid`: `JID` 52 | - `stanza_type`: `unicode` 53 | - `stanza_id`: `unicode` 54 | - `error_cond`: `unicode`""" 55 | self.xmlnode=None 56 | if isinstance(xmlnode,Iq): 57 | pass 58 | elif isinstance(xmlnode,Stanza): 59 | raise TypeError,"Couldn't make Iq from other Stanza" 60 | elif isinstance(xmlnode,libxml2.xmlNode): 61 | pass 62 | elif xmlnode is not None: 63 | raise TypeError,"Couldn't make Iq from %r" % (type(xmlnode),) 64 | elif not stanza_type: 65 | raise ValueError, "type is required for Iq" 66 | else: 67 | if not stanza_id and stanza_type in ("get", "set"): 68 | stanza_id=gen_id() 69 | 70 | if not xmlnode and stanza_type not in ("get","set","result","error"): 71 | raise ValueError, "Invalid Iq type: %r" % (stanza_type,) 72 | 73 | if xmlnode is None: 74 | xmlnode="iq" 75 | 76 | Stanza.__init__(self, xmlnode, from_jid = from_jid, to_jid = to_jid, 77 | stanza_type = stanza_type, stanza_id = stanza_id, error = error, 78 | error_cond = error_cond, stream = stream) 79 | 80 | def copy(self): 81 | """Create a deep copy of the iq stanza. 82 | 83 | :returntype: `Iq`""" 84 | return Iq(self) 85 | 86 | def make_error_response(self,cond): 87 | """Create error response for the a "get" or "set" iq stanza. 88 | 89 | :Parameters: 90 | - `cond`: error condition name, as defined in XMPP specification. 91 | 92 | :return: new `Iq` object with the same "id" as self, "from" and "to" 93 | attributes swapped, type="error" and containing element 94 | plus payload of `self`. 95 | :returntype: `Iq`""" 96 | 97 | if self.get_type() in ("result", "error"): 98 | raise ValueError, "Errors may not be generated for 'result' and 'error' iq" 99 | 100 | iq=Iq(stanza_type="error",from_jid=self.get_to(),to_jid=self.get_from(), 101 | stanza_id=self.get_id(),error_cond=cond) 102 | n=self.get_query() 103 | if n: 104 | n=n.copyNode(1) 105 | iq.xmlnode.children.addPrevSibling(n) 106 | return iq 107 | 108 | def make_result_response(self): 109 | """Create result response for the a "get" or "set" iq stanza. 110 | 111 | :return: new `Iq` object with the same "id" as self, "from" and "to" 112 | attributes replaced and type="result". 113 | :returntype: `Iq`""" 114 | 115 | if self.get_type() not in ("set","get"): 116 | raise ValueError, "Results may only be generated for 'set' or 'get' iq" 117 | 118 | iq=Iq(stanza_type="result", from_jid=self.get_to(), 119 | to_jid=self.get_from(), stanza_id=self.get_id()) 120 | 121 | return iq 122 | 123 | def new_query(self,ns_uri,name="query"): 124 | """Create new payload element for the stanza. 125 | 126 | :Parameters: 127 | - `ns_uri`: namespace URI of the element. 128 | - `name`: element name. 129 | :Types: 130 | - `ns_uri`: `str` 131 | - `name`: `unicode` 132 | 133 | :return: the new payload node. 134 | :returntype: `libxml2.xmlNode`""" 135 | return self.set_new_content(ns_uri,name) 136 | 137 | def get_query(self): 138 | """Get the payload element of the stanza. 139 | 140 | :return: the payload element or None if there is no payload. 141 | :returntype: `libxml2.xmlNode`""" 142 | c = self.xmlnode.children 143 | while c: 144 | try: 145 | if c.ns(): 146 | return c 147 | except libxml2.treeError: 148 | pass 149 | c = c.next 150 | return None 151 | 152 | def get_query_ns(self): 153 | """Get a namespace of the stanza payload. 154 | 155 | :return: XML namespace URI of the payload or None if there is no 156 | payload. 157 | :returntype: `str`""" 158 | q=self.get_query() 159 | if q: 160 | return get_node_ns_uri(q) 161 | else: 162 | return None 163 | 164 | # vi: sts=4 et sw=4 165 | -------------------------------------------------------------------------------- /pyxmpp/jabber/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """JSF defined XMPP extension and legacy Jabber protocol elements""" 19 | 20 | __docformat__="restructuredtext en" 21 | 22 | # vi: sts=4 et sw=4 23 | -------------------------------------------------------------------------------- /pyxmpp/jabber/all.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | # pylint: disable-msg=W0611 18 | 19 | """Convenience module containing most important objects from pyxmpp.jabber 20 | package. 21 | 22 | Suggested usage:: 23 | import pyxmpp.jabber.all 24 | 25 | (imports all important names into pyxmpp.jabber namespace)""" 26 | 27 | __docformat__="restructuredtext en" 28 | 29 | import pyxmpp 30 | import pyxmpp.jabber 31 | 32 | from pyxmpp.jabber.clientstream import LegacyClientStream 33 | from pyxmpp.jabber.client import JabberClient as Client 34 | from pyxmpp.jabber.disco import DISCO_NS,DISCO_INFO_NS,DISCO_ITEMS_NS 35 | from pyxmpp.jabber.disco import DiscoInfo,DiscoItems,DiscoItem,DiscoIdentity 36 | from pyxmpp.jabber.vcard import VCARD_NS,VCard 37 | from pyxmpp.jabber.register import Register 38 | 39 | for name in dir(): 40 | if not name.startswith("__") and name!="pyxmpp": 41 | setattr(pyxmpp.jabber,name,globals()[name]) 42 | 43 | # vi: sts=4 et sw=4 44 | -------------------------------------------------------------------------------- /pyxmpp/jabber/delay.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Delayed delivery mark (urn:xmpp:delay and jabber:x:delay) handling. 19 | 20 | Normative reference: 21 | - `JEP 91 `__ 22 | - `XEP 0203 `__ 23 | """ 24 | 25 | __docformat__="restructuredtext en" 26 | 27 | import libxml2 28 | import time 29 | import datetime 30 | 31 | from pyxmpp.jid import JID 32 | 33 | from pyxmpp.utils import to_utf8,from_utf8 34 | from pyxmpp.xmlextra import get_node_ns_uri 35 | from pyxmpp.utils import datetime_utc_to_local,datetime_local_to_utc 36 | from pyxmpp.objects import StanzaPayloadObject 37 | from pyxmpp.exceptions import BadRequestProtocolError, JIDMalformedProtocolError, JIDError 38 | 39 | DELAY_NS = "urn:xmpp:delay" 40 | LEGACY_DELAY_NS = "jabber:x:delay" 41 | 42 | def _parse_ts(timestamp): 43 | if "." in timestamp and timestamp.endswith("Z"): 44 | # strip miliseconds 45 | timestamp = timestamp.split(".", 1)[0] + "Z" 46 | for fmt in ("%Y-%m-%dT%H:%M:%SZ", "%Y%m%dT%H:%M:%S"): 47 | try: 48 | result = time.strptime(timestamp, fmt) 49 | return result 50 | except ValueError: 51 | continue 52 | raise BadRequestProtocolError, "Bad timestamp: " + repr(timestamp) 53 | 54 | class Delay(StanzaPayloadObject): 55 | """ 56 | Delayed delivery tag. 57 | 58 | Represents 'urn:xmpp:delay' (XEP-0203) element of a Jabber stanza. 59 | 60 | :Ivariables: 61 | - `delay_from`: the "from" value of the delay element 62 | - `reason`: the "reason" (content) of the delay element 63 | - `timestamp`: the UTC timestamp as naive datetime object 64 | """ 65 | 66 | xml_element_name = "delay" 67 | xml_element_namespace = DELAY_NS 68 | 69 | _sort_order = 1 70 | _time_format = "%Y-%m-%dT%H:%M:%SZ" 71 | 72 | def __init__(self,node_or_datetime,delay_from=None,reason=None,utc=True): 73 | """ 74 | Initialize the Delay object. 75 | 76 | :Parameters: 77 | - `node_or_datetime`: an XML node to parse or the timestamp. 78 | - `delay_from`: JID of the entity which adds the delay mark 79 | (when `node_or_datetime` is a timestamp). 80 | - `reason`: reason of the delay (when `node_or_datetime` is a 81 | timestamp). 82 | - `utc`: if `True` then the timestamp is assumed to be UTC, 83 | otherwise it is assumed to be local time. 84 | :Types: 85 | - `node_or_datetime`: `libxml2.xmlNode` or `datetime.datetime` 86 | - `delay_from`: `pyxmpp.JID` 87 | - `reason`: `unicode` 88 | - `utc`: `bool`""" 89 | if isinstance(node_or_datetime,libxml2.xmlNode): 90 | self.from_xml(node_or_datetime) 91 | else: 92 | if utc: 93 | self.timestamp=node_or_datetime 94 | else: 95 | self.timestamp=datetime_local_to_utc(node_or_datetime) 96 | self.delay_from=JID(delay_from) 97 | self.reason=unicode(reason) 98 | 99 | def from_xml(self,xmlnode): 100 | """Initialize Delay object from an XML node. 101 | 102 | :Parameters: 103 | - `xmlnode`: the jabber:x:delay XML element. 104 | :Types: 105 | - `xmlnode`: `libxml2.xmlNode`""" 106 | if xmlnode.type!="element": 107 | raise ValueError,"XML node is not a jabber:x:delay element (not an element)" 108 | ns=get_node_ns_uri(xmlnode) 109 | if ns and (ns != self.xml_element_namespace 110 | or xmlnode.name != self.xml_element_name): 111 | raise ValueError,"XML node is not a " + self.xml_element_namespace + " element" 112 | stamp=xmlnode.prop("stamp") 113 | tm = _parse_ts(stamp) 114 | tm=tm[0:8]+(0,) 115 | self.timestamp=datetime.datetime.fromtimestamp(time.mktime(tm)) 116 | delay_from=from_utf8(xmlnode.prop("from")) 117 | if delay_from: 118 | try: 119 | self.delay_from = JID(delay_from) 120 | except JIDError: 121 | raise JIDMalformedProtocolError, "Bad JID in the " + self.xml_element_namespace + " 'from' attribute" 122 | else: 123 | self.delay_from = None 124 | self.reason = from_utf8(xmlnode.getContent()) 125 | 126 | def complete_xml_element(self, xmlnode, _unused): 127 | """Complete the XML node with `self` content. 128 | 129 | Should be overriden in classes derived from `StanzaPayloadObject`. 130 | 131 | :Parameters: 132 | - `xmlnode`: XML node with the element being built. It has already 133 | right name and namespace, but no attributes or content. 134 | - `_unused`: document to which the element belongs. 135 | :Types: 136 | - `xmlnode`: `libxml2.xmlNode` 137 | - `_unused`: `libxml2.xmlDoc`""" 138 | tm=self.timestamp.strftime(self._time_format) 139 | xmlnode.setProp("stamp",tm) 140 | if self.delay_from: 141 | xmlnode.setProp("from",self.delay_from.as_utf8()) 142 | if self.reason: 143 | xmlnode.setContent(to_utf8(self.reason)) 144 | 145 | def get_datetime_local(self): 146 | """Get the timestamp as a local time. 147 | 148 | :return: the timestamp of the delay element represented in the local 149 | timezone. 150 | :returntype: `datetime.datetime`""" 151 | r=datetime_utc_to_local(self.timestamp) 152 | return r 153 | 154 | def get_datetime_utc(self): 155 | """Get the timestamp as a UTC. 156 | 157 | :return: the timestamp of the delay element represented in UTC. 158 | :returntype: `datetime.datetime`""" 159 | return self.timestamp 160 | 161 | def __str__(self): 162 | n=self.as_xml() 163 | r=n.serialize() 164 | n.freeNode() 165 | return r 166 | 167 | def __cmp__(self, other): 168 | return cmp((self._sort_order, self.timestamp), 169 | (other._sort_order, other.timestamp)) 170 | 171 | class LegacyDelay(Delay): 172 | """ 173 | Delayed delivery tag. 174 | 175 | Represents 'jabber:x:delay' (JEP-0091) element of a Jabber stanza. 176 | 177 | :Ivariables: 178 | - `delay_from`: the "from" value of the delay element 179 | - `reason`: the "reason" (content) of the delay element 180 | - `timestamp`: the UTC timestamp as naive datetime object 181 | """ 182 | 183 | xml_element_name = "x" 184 | xml_element_namespace = LEGACY_DELAY_NS 185 | 186 | _sort_order = 2 187 | _time_format = "%Y%m%dT%H:%M:%S" 188 | 189 | def get_delays(stanza): 190 | """Get jabber:x:delay elements from the stanza. 191 | 192 | :Parameters: 193 | - `stanza`: a, probably delayed, stanza. 194 | :Types: 195 | - `stanza`: `pyxmpp.stanza.Stanza` 196 | 197 | :return: list of delay tags sorted by the timestamp. 198 | :returntype: `list` of `Delay`""" 199 | delays=[] 200 | n=stanza.xmlnode.children 201 | while n: 202 | if n.type=="element": 203 | if get_node_ns_uri(n) == DELAY_NS and n.name == "delay": 204 | delays.append(Delay(n)) 205 | elif get_node_ns_uri(n) == LEGACY_DELAY_NS and n.name == "x": 206 | delays.append(LegacyDelay(n)) 207 | n=n.next 208 | delays.sort() 209 | return delays 210 | 211 | def get_delay(stanza): 212 | """Get the oldest jabber:x:delay elements from the stanza. 213 | 214 | :Parameters: 215 | - `stanza`: a, probably delayed, stanza. 216 | :Types: 217 | - `stanza`: `pyxmpp.stanza.Stanza` 218 | 219 | The return value, if not `None`, contains a quite reliable 220 | timestamp of a delayed (e.g. from offline storage) message. 221 | 222 | :return: the oldest delay tag of the stanza or `None`. 223 | :returntype: `Delay`""" 224 | delays=get_delays(stanza) 225 | if not delays: 226 | return None 227 | return get_delays(stanza)[0] 228 | 229 | # vi: sts=4 et sw=4 230 | -------------------------------------------------------------------------------- /pyxmpp/jabber/simple.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2005-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | # pylint: disable-msg=W0232, E0201 18 | 19 | """Simple API for simple things like sendig messages or single stanzas.""" 20 | 21 | __docformat__="restructuredtext en" 22 | 23 | def xmpp_do(jid,password,function,server=None,port=None): 24 | """Connect as client to a Jabber/XMPP server and call the provided 25 | function when stream is ready for IM. The function will be called 26 | with one argument -- the XMPP stream. After function returns the stream is 27 | closed.""" 28 | from pyxmpp.jabber.client import JabberClient 29 | class Client(JabberClient): 30 | """The simplest client implementation.""" 31 | def session_started(self): 32 | """Call the function provided when the session starts and exit.""" 33 | function(self.stream) 34 | self.disconnect() 35 | c=Client(jid,password,server=server,port=port) 36 | c.connect() 37 | try: 38 | c.loop(1) 39 | except KeyboardInterrupt: 40 | print u"disconnecting..." 41 | c.disconnect() 42 | 43 | def send_message(my_jid, my_password, to_jid, body, subject=None, 44 | message_type=None, server=None, port=None): 45 | """Star an XMPP session and send a message, then exit. 46 | 47 | :Parameters: 48 | - `my_jid`: sender JID. 49 | - `my_password`: sender password. 50 | - `to_jid`: recipient JID. 51 | - `body`: message body. 52 | - `subject`: message subject. 53 | - `message_type`: message type. 54 | - `server`: server to connect to (default: derivied from `my_jid` using 55 | DNS records). 56 | - `port`: TCP port number to connect to (default: retrieved using SRV 57 | DNS record, or 5222). 58 | :Types: 59 | - `my_jid`: `pyxmpp.jid.JID` 60 | - `my_password`: `unicode` 61 | - `to_jid`: `pyxmpp.jid.JID` 62 | - `body`: `unicode` 63 | - `subject`: `unicode` 64 | - `message_type`: `str` 65 | - `server`: `unicode` or `str` 66 | - `port`: `int` 67 | """ 68 | from pyxmpp.message import Message 69 | msg=Message(to_jid=to_jid,body=body,subject=subject,stanza_type=message_type) 70 | def fun(stream): 71 | """Send a mesage `msg` via a stream.""" 72 | stream.send(msg) 73 | xmpp_do(my_jid,my_password,fun,server,port) 74 | 75 | # vi: sts=4 et sw=4 76 | -------------------------------------------------------------------------------- /pyxmpp/jabberd/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Facilities for jabber server implementation specific features, 19 | like components.""" 20 | 21 | __docformat__="restructuredtext en" 22 | 23 | # vi: sts=4 et sw=4 24 | -------------------------------------------------------------------------------- /pyxmpp/jabberd/all.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | # pylint: disable-msg=W0611 18 | 19 | """Convenience module containing most important objects fr pyxmpp.jabberd package. 20 | 21 | Suggested usage:: 22 | import pyxmpp.jabberd.all 23 | 24 | (imports all important names into pyxmpp.jabberd namespace)""" 25 | 26 | __docformat__="restructuredtext en" 27 | 28 | import pyxmpp.jabberd 29 | 30 | from pyxmpp.jabberd.componentstream import ComponentStream 31 | from pyxmpp.jabberd.component import Component 32 | 33 | for name in dir(): 34 | if not name.startswith("__") and name!="pyxmpp": 35 | setattr(pyxmpp.jabberd,name,globals()[name]) 36 | 37 | # vi: sts=4 et sw=4 38 | -------------------------------------------------------------------------------- /pyxmpp/jabberd/componentstream.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | # pylint: disable-msg=W0221, W0201 18 | """Component (jabber:component:accept) stream handling. 19 | 20 | Normative reference: 21 | - `JEP 114 `__ 22 | """ 23 | 24 | __docformat__="restructuredtext en" 25 | 26 | import hashlib 27 | 28 | import logging 29 | 30 | from pyxmpp.stream import Stream 31 | from pyxmpp.streambase import stanza_factory,HostMismatch 32 | from pyxmpp.xmlextra import common_doc,common_root 33 | from pyxmpp.utils import to_utf8 34 | from pyxmpp.exceptions import StreamError,FatalStreamError,ComponentStreamError,FatalComponentStreamError 35 | 36 | class ComponentStream(Stream): 37 | """Handles jabberd component (jabber:component:accept) connection stream. 38 | 39 | :Ivariables: 40 | - `server`: server to use. 41 | - `port`: port number to use. 42 | - `secret`: authentication secret. 43 | :Types: 44 | - `server`: `unicode` 45 | - `port`: `int` 46 | - `secret`: `unicode`""" 47 | 48 | def __init__(self, jid, secret, server, port, keepalive = 0, owner = None): 49 | """Initialize a `ComponentStream` object. 50 | 51 | :Parameters: 52 | - `jid`: JID of the component. 53 | - `secret`: authentication secret. 54 | - `server`: server address. 55 | - `port`: TCP port number on the server. 56 | - `keepalive`: keepalive interval. 0 to disable. 57 | - `owner`: `Client`, `Component` or similar object "owning" this stream. 58 | """ 59 | Stream.__init__(self, "jabber:component:accept", 60 | sasl_mechanisms = [], 61 | tls_settings = None, 62 | keepalive = keepalive, 63 | owner = owner) 64 | self.server=server 65 | self.port=port 66 | self.me=jid 67 | self.secret=secret 68 | self.process_all_stanzas=1 69 | self.__logger=logging.getLogger("pyxmpp.jabberd.ComponentStream") 70 | 71 | def _reset(self): 72 | """Reset `ComponentStream` object state, making the object ready to 73 | handle new connections.""" 74 | Stream._reset(self) 75 | 76 | def connect(self,server=None,port=None): 77 | """Establish a client connection to a server. 78 | 79 | [component only] 80 | 81 | :Parameters: 82 | - `server`: name or address of the server to use. If not given 83 | then use the one specified when creating the object. 84 | - `port`: port number of the server to use. If not given then use 85 | the one specified when creating the object. 86 | 87 | :Types: 88 | - `server`: `unicode` 89 | - `port`: `int`""" 90 | self.lock.acquire() 91 | try: 92 | self._connect(server,port) 93 | finally: 94 | self.lock.release() 95 | 96 | def _connect(self,server=None,port=None): 97 | """Same as `ComponentStream.connect` but assume `self.lock` is acquired.""" 98 | if self.me.node or self.me.resource: 99 | raise Value, "Component JID may have only domain defined" 100 | if not server: 101 | server=self.server 102 | if not port: 103 | port=self.port 104 | if not server or not port: 105 | raise ValueError, "Server or port not given" 106 | Stream._connect(self,server,port,None,self.me) 107 | 108 | def accept(self,sock): 109 | """Accept an incoming component connection. 110 | 111 | [server only] 112 | 113 | :Parameters: 114 | - `sock`: a listening socket.""" 115 | Stream.accept(self,sock,None) 116 | 117 | def stream_start(self,doc): 118 | """Process (stream start) tag received from peer. 119 | 120 | Call `Stream.stream_start`, but ignore any `HostMismatch` error. 121 | 122 | :Parameters: 123 | - `doc`: document created by the parser""" 124 | try: 125 | Stream.stream_start(self,doc) 126 | except HostMismatch: 127 | pass 128 | 129 | def _post_connect(self): 130 | """Initialize authentication when the connection is established 131 | and we are the initiator.""" 132 | if self.initiator: 133 | self._auth() 134 | 135 | def _compute_handshake(self): 136 | """Compute the authentication handshake value. 137 | 138 | :return: the computed hash value. 139 | :returntype: `str`""" 140 | return hashlib.sha1(to_utf8(self.stream_id)+to_utf8(self.secret)).hexdigest() 141 | 142 | def _auth(self): 143 | """Authenticate on the server. 144 | 145 | [component only]""" 146 | if self.authenticated: 147 | self.__logger.debug("_auth: already authenticated") 148 | return 149 | self.__logger.debug("doing handshake...") 150 | hash_value=self._compute_handshake() 151 | n=common_root.newTextChild(None,"handshake",hash_value) 152 | self._write_node(n) 153 | n.unlinkNode() 154 | n.freeNode() 155 | self.__logger.debug("handshake hash sent.") 156 | 157 | def _process_node(self,node): 158 | """Process first level element of the stream. 159 | 160 | Handle component handshake (authentication) element, and 161 | treat elements in "jabber:component:accept", "jabber:client" 162 | and "jabber:server" equally (pass to `self.process_stanza`). 163 | All other elements are passed to `Stream._process_node`. 164 | 165 | :Parameters: 166 | - `node`: XML node describing the element 167 | """ 168 | ns=node.ns() 169 | if ns: 170 | ns_uri=node.ns().getContent() 171 | if (not ns or ns_uri=="jabber:component:accept") and node.name=="handshake": 172 | if self.initiator and not self.authenticated: 173 | self.authenticated=1 174 | self.state_change("authenticated",self.me) 175 | self._post_auth() 176 | return 177 | elif not self.authenticated and node.getContent()==self._compute_handshake(): 178 | self.peer=self.me 179 | n=common_doc.newChild(None,"handshake",None) 180 | self._write_node(n) 181 | n.unlinkNode() 182 | n.freeNode() 183 | self.peer_authenticated=1 184 | self.state_change("authenticated",self.peer) 185 | self._post_auth() 186 | return 187 | else: 188 | self._send_stream_error("not-authorized") 189 | raise FatalComponentStreamError,"Hanshake error." 190 | 191 | if ns_uri in ("jabber:component:accept","jabber:client","jabber:server"): 192 | stanza=stanza_factory(node) 193 | self.lock.release() 194 | try: 195 | self.process_stanza(stanza) 196 | finally: 197 | self.lock.acquire() 198 | stanza.free() 199 | return 200 | return Stream._process_node(self,node) 201 | 202 | # vi: sts=4 et sw=4 203 | -------------------------------------------------------------------------------- /pyxmpp/message.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Message XMPP stanza handling 19 | 20 | Normative reference: 21 | - `RFC 3920 `__ 22 | """ 23 | 24 | __docformat__="restructuredtext en" 25 | 26 | import libxml2 27 | from pyxmpp.stanza import Stanza 28 | from pyxmpp.utils import to_utf8,from_utf8 29 | from pyxmpp.xmlextra import common_ns 30 | 31 | message_types=("normal","chat","headline","error","groupchat") 32 | 33 | class Message(Stanza): 34 | """Wraper object for stanzas.""" 35 | stanza_type="message" 36 | def __init__(self, xmlnode = None, from_jid = None, to_jid = None, stanza_type = None, stanza_id = None, 37 | subject = None, body = None, thread = None, error = None, error_cond = None, stream = None): 38 | """Initialize a `Message` object. 39 | 40 | :Parameters: 41 | - `xmlnode`: XML node to_jid be wrapped into the `Message` object 42 | or other Message object to be copied. If not given then new 43 | presence stanza is created using following parameters. 44 | - `from_jid`: sender JID. 45 | - `to_jid`: recipient JID. 46 | - `stanza_type`: staza type: one of: "get", "set", "result" or "error". 47 | - `stanza_id`: stanza id -- value of stanza's "id" attribute. If not 48 | given, then unique for the session value is generated. 49 | - `subject`: message subject, 50 | - `body`: message body. 51 | - `thread`: message thread id. 52 | - `error_cond`: error condition name. Ignored if `stanza_type` is not "error". 53 | :Types: 54 | - `xmlnode`: `unicode` or `libxml2.xmlNode` or `Stanza` 55 | - `from_jid`: `JID` 56 | - `to_jid`: `JID` 57 | - `stanza_type`: `unicode` 58 | - `stanza_id`: `unicode` 59 | - `subject`: `unicode` 60 | - `body`: `unicode` 61 | - `thread`: `unicode` 62 | - `error_cond`: `unicode`""" 63 | 64 | self.xmlnode=None 65 | if isinstance(xmlnode,Message): 66 | pass 67 | elif isinstance(xmlnode,Stanza): 68 | raise TypeError, "Couldn't make Message from other Stanza" 69 | elif isinstance(xmlnode,libxml2.xmlNode): 70 | pass 71 | elif xmlnode is not None: 72 | raise TypeError, "Couldn't make Message from %r" % (type(xmlnode),) 73 | 74 | if xmlnode is None: 75 | xmlnode="message" 76 | 77 | Stanza.__init__(self, xmlnode, from_jid = from_jid, to_jid = to_jid, stanza_type = stanza_type, 78 | stanza_id = stanza_id, error = error, error_cond = error_cond, stream = stream) 79 | 80 | if subject is not None: 81 | self.xmlnode.newTextChild(common_ns,"subject",to_utf8(subject)) 82 | if body is not None: 83 | self.xmlnode.newTextChild(common_ns,"body",to_utf8(body)) 84 | if thread is not None: 85 | self.xmlnode.newTextChild(common_ns,"thread",to_utf8(thread)) 86 | 87 | def get_subject(self): 88 | """Get the message subject. 89 | 90 | :return: the message subject or `None` if there is no subject. 91 | :returntype: `unicode`""" 92 | n=self.xpath_eval("ns:subject") 93 | if n: 94 | return from_utf8(n[0].getContent()) 95 | else: 96 | return None 97 | 98 | def get_thread(self): 99 | """Get the thread-id subject. 100 | 101 | :return: the thread-id or `None` if there is no thread-id. 102 | :returntype: `unicode`""" 103 | n=self.xpath_eval("ns:thread") 104 | if n: 105 | return from_utf8(n[0].getContent()) 106 | else: 107 | return None 108 | 109 | def copy(self): 110 | """Create a deep copy of the message stanza. 111 | 112 | :returntype: `Message`""" 113 | return Message(self) 114 | 115 | def get_body(self): 116 | """Get the body of the message. 117 | 118 | :return: the body of the message or `None` if there is no body. 119 | :returntype: `unicode`""" 120 | n=self.xpath_eval("ns:body") 121 | if n: 122 | return from_utf8(n[0].getContent()) 123 | else: 124 | return None 125 | 126 | def make_error_response(self,cond): 127 | """Create error response for any non-error message stanza. 128 | 129 | :Parameters: 130 | - `cond`: error condition name, as defined in XMPP specification. 131 | 132 | :return: new message stanza with the same "id" as self, "from" and 133 | "to" attributes swapped, type="error" and containing 134 | element plus payload of `self`. 135 | :returntype: `unicode`""" 136 | 137 | if self.get_type() == "error": 138 | raise ValueError, "Errors may not be generated in response to errors" 139 | 140 | m=Message(stanza_type="error",from_jid=self.get_to(),to_jid=self.get_from(), 141 | stanza_id=self.get_id(),error_cond=cond) 142 | 143 | if self.xmlnode.children: 144 | n=self.xmlnode.children 145 | while n: 146 | m.xmlnode.children.addPrevSibling(n.copyNode(1)) 147 | n=n.next 148 | return m 149 | 150 | # vi: sts=4 et sw=4 151 | -------------------------------------------------------------------------------- /pyxmpp/objects.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | # pylint: disable-msg=W0232, E0201 18 | 19 | """General base classes for PyXMPP objects.""" 20 | 21 | __docformat__="restructuredtext en" 22 | 23 | import libxml2 24 | from pyxmpp.xmlextra import common_doc 25 | 26 | class StanzaPayloadObject(object): 27 | """Base class for objects that may be used as XMPP stanza payload and don't keep 28 | internal XML representation, only parsed values. 29 | 30 | Provides `as_xml` method. Derived classes must override `xml_element_name` and 31 | `xml_element_namespace` class attributes and the `complete_xml_element` method. 32 | 33 | Please note that not all classes derived from `StanzaPayloadObject` should be 34 | used directly as stanza payload. Some of them are parts of higher level objects. 35 | 36 | :Cvariables: 37 | - `xml_element_name`: name for the XML element provided by the class. 38 | - `xml_element_namespace`: namespace URI for the XML element provided 39 | by the class. 40 | :Types: 41 | - `xml_element_name`: `unicode` 42 | - `xml_element_namespace`: `unicode` 43 | """ 44 | xml_element_name = None 45 | xml_element_namespace = None 46 | 47 | def as_xml(self, parent = None, doc = None): 48 | """Get the XML representation of `self`. 49 | 50 | New document will be created if no `parent` and no `doc` is given. 51 | 52 | :Parameters: 53 | - `parent`: the parent for the XML element. 54 | - `doc`: the document where the element should be created. If not 55 | given and `parent` is provided then autodetection is attempted. 56 | If that fails, then `common_doc` is used. 57 | :Types: 58 | - `parent`: `libxml2.xmlNode` 59 | - `doc`: `libxml2.xmlDoc` 60 | :return: the new XML element or document created. 61 | :returntype: `libxml2.xmlNode` or `libxml2.xmlDoc`""" 62 | if parent: 63 | if not doc: 64 | n = parent 65 | while n: 66 | if n.type == "xml_document": 67 | doc = n 68 | break 69 | n = n.parent 70 | if not doc: 71 | doc = common_doc 72 | try: 73 | ns = parent.searchNsByHref(doc, self.xml_element_namespace) 74 | except libxml2.treeError: 75 | ns = None 76 | xmlnode = parent.newChild(ns,self.xml_element_name,None) 77 | if not ns: 78 | ns = xmlnode.newNs(self.xml_element_namespace,None) 79 | xmlnode.setNs(ns) 80 | doc1 = doc 81 | else: 82 | if doc: 83 | doc1 = doc 84 | else: 85 | doc1 = libxml2.newDoc("1.0") 86 | xmlnode = doc1.newChild(None,self.xml_element_name, None) 87 | ns = xmlnode.newNs(self.xml_element_namespace, None) 88 | xmlnode.setNs(ns) 89 | 90 | self.complete_xml_element(xmlnode, doc1) 91 | 92 | if doc or parent: 93 | return xmlnode 94 | doc1.setRootElement(xmlnode) 95 | return doc1 96 | 97 | def complete_xml_element(self, xmlnode, doc): 98 | """Complete the XML node with `self` content. 99 | 100 | Should be overriden in classes derived from `StanzaPayloadObject`. 101 | 102 | :Parameters: 103 | - `xmlnode`: XML node with the element being built. It has already 104 | right name and namespace, but no attributes or content. 105 | - `doc`: document to which the element belongs. 106 | :Types: 107 | - `xmlnode`: `libxml2.xmlNode` 108 | - `doc`: `libxml2.xmlDoc`""" 109 | pass 110 | 111 | class StanzaPayloadWrapperObject(object): 112 | """Base class for objects that may be used as XMPP stanza payload and maintain 113 | an internal XML representation of self. 114 | 115 | Provides `as_xml` method. Objects of derived classes must have the `xmlnode` attribute. 116 | 117 | Please note that not all classes derived from `StanzaPayloadWrapperObject` should be 118 | used directly as stanza payload. Some of them are parts of higher level objects. 119 | 120 | :Ivariables: 121 | - `xmlnode`: XML node of the object. 122 | :Types: 123 | - `xmlnode`: `libxml2.xmlNode` 124 | """ 125 | 126 | def as_xml(self, parent = None, doc = None): 127 | """Get the XML representation of `self`. 128 | 129 | New document will be created if no `parent` and no `doc` is given. 130 | 131 | :Parameters: 132 | - `parent`: the parent for the XML element. 133 | - `doc`: the document where the element should be created. If not 134 | given and `parent` is provided then autodetection is attempted. 135 | If that fails, then `common_doc` is used. 136 | :Types: 137 | - `parent`: `libxml2.xmlNode` 138 | - `doc`: `libxml2.xmlDoc` 139 | 140 | :return: the new XML element (copy of `self.xmlnode`) or document 141 | created (containg the copy as the root element). 142 | :returntype: `libxml2.xmlNode` or `libxml2.xmlDoc`""" 143 | if parent: 144 | if not doc: 145 | n = parent 146 | while n: 147 | if n.type == "xml_document": 148 | doc = n 149 | break 150 | n = n.parent 151 | if not doc: 152 | doc = common_doc 153 | copy=self.xmlnode.docCopyNode(doc,True) 154 | parent.addChild(copy) 155 | return copy 156 | else: 157 | if not doc: 158 | doc1=libxml2.newDoc("1.0") 159 | else: 160 | doc1=doc 161 | xmlnode=doc1.addChild(self.xmlnode.docCopyNode(doc,True)) 162 | doc1.setRootElement(xmlnode) 163 | if doc: 164 | return xmlnode 165 | return doc1 166 | 167 | # vi: sts=4 et sw=4 168 | -------------------------------------------------------------------------------- /pyxmpp/resolver.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """DNS resolever with SRV record support. 19 | 20 | Normative reference: 21 | - `RFC 1035 `__ 22 | - `RFC 2782 `__ 23 | """ 24 | 25 | __docformat__="restructuredtext en" 26 | 27 | import re 28 | import socket 29 | from socket import AF_UNSPEC, AF_INET, AF_INET6 30 | import dns.resolver 31 | import dns.name 32 | import dns.exception 33 | import random 34 | import logging 35 | 36 | from .exceptions import DNSError, UnexpectedCNAMEError 37 | 38 | logger = logging.getLogger("pyxmpp.resolver") 39 | 40 | # check IPv6 support 41 | try: 42 | socket.socket(AF_INET6) 43 | except socket.error: 44 | default_address_family = AF_INET 45 | else: 46 | default_address_family = AF_UNSPEC 47 | 48 | def set_default_address_family(family): 49 | """Select default address family. 50 | 51 | :Parameters: 52 | - `family`: `AF_INET` for IPv4, `AF_INET6` for IPv6 and `AF_UNSPEC` for 53 | dual stack.""" 54 | global default_address_family 55 | default_address_family = family 56 | 57 | service_aliases={"xmpp-server": ("jabber-server","jabber")} 58 | 59 | # should match all valid IP addresses, but can pass some false-positives, 60 | # which are not valid domain names 61 | ipv4_re=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") 62 | ipv6_re=re.compile(r"^[0-9a-f]{0,4}:[0-9a-f:]{0,29}:([0-9a-f]{0,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$") 63 | 64 | def shuffle_srv(records): 65 | """Randomly reorder SRV records using their weights. 66 | 67 | :Parameters: 68 | - `records`: SRV records to shuffle. 69 | :Types: 70 | - `records`: sequence of `dns.rdtypes.IN.SRV` 71 | 72 | :return: reordered records. 73 | :returntype: `list` of `dns.rdtypes.IN.SRV`""" 74 | if not records: 75 | return [] 76 | ret=[] 77 | while len(records)>1: 78 | weight_sum=0 79 | for rr in records: 80 | weight_sum+=rr.weight+0.1 81 | thres=random.random()*weight_sum 82 | weight_sum=0 83 | for rr in records: 84 | weight_sum+=rr.weight+0.1 85 | if thres 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | """SASL authentication implementaion for PyXMPP. 18 | 19 | Normative reference: 20 | - `RFC 2222 `__ 21 | """ 22 | 23 | __docformat__="restructuredtext en" 24 | 25 | import random 26 | 27 | from pyxmpp.sasl.core import Reply,Response,Challenge,Success,Failure,PasswordManager 28 | 29 | from pyxmpp.sasl.plain import PlainClientAuthenticator,PlainServerAuthenticator 30 | from pyxmpp.sasl.digest_md5 import DigestMD5ClientAuthenticator,DigestMD5ServerAuthenticator 31 | from pyxmpp.sasl.external import ExternalClientAuthenticator 32 | 33 | safe_mechanisms_dict={"DIGEST-MD5":(DigestMD5ClientAuthenticator,DigestMD5ServerAuthenticator), 34 | "EXTERNAL":(ExternalClientAuthenticator, None)} 35 | try: 36 | from pyxmpp.sasl.gssapi import GSSAPIClientAuthenticator 37 | except ImportError: 38 | pass # Kerberos not available 39 | else: 40 | safe_mechanisms_dict["GSSAPI"] = (GSSAPIClientAuthenticator,None) 41 | unsafe_mechanisms_dict={"PLAIN":(PlainClientAuthenticator,PlainServerAuthenticator)} 42 | all_mechanisms_dict=safe_mechanisms_dict.copy() 43 | all_mechanisms_dict.update(unsafe_mechanisms_dict) 44 | 45 | safe_mechanisms=safe_mechanisms_dict.keys() 46 | unsafe_mechanisms=unsafe_mechanisms_dict.keys() 47 | all_mechanisms=safe_mechanisms+unsafe_mechanisms 48 | 49 | def client_authenticator_factory(mechanism,password_manager): 50 | """Create a client authenticator object for given SASL mechanism and 51 | password manager. 52 | 53 | :Parameters: 54 | - `mechanism`: name of the SASL mechanism ("PLAIN", "DIGEST-MD5" or "GSSAPI"). 55 | - `password_manager`: name of the password manager object providing 56 | authentication credentials. 57 | :Types: 58 | - `mechanism`: `str` 59 | - `password_manager`: `PasswordManager` 60 | 61 | :return: new authenticator. 62 | :returntype: `sasl.core.ClientAuthenticator`""" 63 | authenticator=all_mechanisms_dict[mechanism][0] 64 | return authenticator(password_manager) 65 | 66 | def server_authenticator_factory(mechanism,password_manager): 67 | """Create a server authenticator object for given SASL mechanism and 68 | password manager. 69 | 70 | :Parameters: 71 | - `mechanism`: name of the SASL mechanism ("PLAIN", "DIGEST-MD5" or "GSSAPI"). 72 | - `password_manager`: name of the password manager object to be used 73 | for authentication credentials verification. 74 | :Types: 75 | - `mechanism`: `str` 76 | - `password_manager`: `PasswordManager` 77 | 78 | :return: new authenticator. 79 | :returntype: `sasl.core.ServerAuthenticator`""" 80 | authenticator=all_mechanisms_dict[mechanism][1] 81 | return authenticator(password_manager) 82 | 83 | # vi: sts=4 et sw=4 84 | -------------------------------------------------------------------------------- /pyxmpp/sasl/external.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2009 Michal Witkowski 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | """External SASL authentication mechanism for PyXMPP SASL implementation. 18 | 19 | Normative reference: 20 | - `RFC 3920bis `__ 21 | - `XEP-0178 __` 22 | """ 23 | 24 | __docformat__="restructuredtext en" 25 | 26 | import base64 27 | 28 | import logging 29 | 30 | from pyxmpp.sasl.core import (ClientAuthenticator,Failure,Response,Challenge,Success) 31 | 32 | class ExternalClientAuthenticator(ClientAuthenticator): 33 | """Provides client-side External SASL (TLS-Identify) authentication.""" 34 | 35 | 36 | def __init__(self,password_manager): 37 | ClientAuthenticator.__init__(self, password_manager) 38 | self.password_manager = password_manager 39 | self.__logger = logging.getLogger("pyxmpp.sasl.external.ExternalClientAuthenticator") 40 | 41 | def start(self, username, authzid): 42 | self.username = username 43 | self.authzid = authzid 44 | # TODO: This isn't very XEP-0178'ish. 45 | # XEP-0178 says "=" should be sent when only one id-on-xmppAddr is 46 | # in the cert, but we don't know that. Still, this conforms to the 47 | # standard and works. 48 | return Response(self.authzid, encode = True) 49 | #return Response("=", encode = False) 50 | 51 | def finish(self,data): 52 | """Handle authentication success information from the server. 53 | 54 | :Parameters: 55 | - `data`: the optional additional data returned with the success. 56 | :Types: 57 | - `data`: `str` 58 | 59 | :return: a success indicator. 60 | :returntype: `Success`""" 61 | _unused = data 62 | return Success(self.username,None,self.authzid) 63 | 64 | # vi: sts=4 et sw=4 65 | -------------------------------------------------------------------------------- /pyxmpp/sasl/gssapi.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2008 Jelmer Vernooij 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | """GSSAPI authentication mechanism for PyXMPP SASL implementation. 18 | 19 | Normative reference: 20 | - `RFC 4752 `__ 21 | """ 22 | 23 | __docformat__="restructuredtext en" 24 | 25 | import base64 26 | import kerberos 27 | 28 | import logging 29 | 30 | from pyxmpp.sasl.core import (ClientAuthenticator,Failure,Response,Challenge,Success) 31 | 32 | class GSSAPIClientAuthenticator(ClientAuthenticator): 33 | """Provides client-side GSSAPI SASL (Kerberos 5) authentication.""" 34 | 35 | def __init__(self,password_manager): 36 | ClientAuthenticator.__init__(self, password_manager) 37 | self.password_manager = password_manager 38 | self.__logger = logging.getLogger("pyxmpp.sasl.gssapi.GSSAPIClientAuthenticator") 39 | 40 | def start(self, username, authzid): 41 | self.username = username 42 | self.authzid = authzid 43 | rc, self._gss = kerberos.authGSSClientInit(authzid or "%s@%s" % ("xmpp", self.password_manager.get_serv_host())) 44 | self.step = 0 45 | return self.challenge("") 46 | 47 | def challenge(self, challenge): 48 | if self.step == 0: 49 | rc = kerberos.authGSSClientStep(self._gss, base64.b64encode(challenge)) 50 | if rc != kerberos.AUTH_GSS_CONTINUE: 51 | self.step = 1 52 | elif self.step == 1: 53 | rc = kerberos.authGSSClientUnwrap(self._gss, base64.b64encode(challenge)) 54 | response = kerberos.authGSSClientResponse(self._gss) 55 | rc = kerberos.authGSSClientWrap(self._gss, response, self.username) 56 | response = kerberos.authGSSClientResponse(self._gss) 57 | if response is None: 58 | return Response("") 59 | else: 60 | return Response(base64.b64decode(response)) 61 | 62 | def finish(self, data): 63 | self.username = kerberos.authGSSClientUserName(self._gss) 64 | self.__logger.debug("Authenticated as %s" % kerberos.authGSSClientUserName(self._gss)) 65 | return Success(self.username,None,self.authzid) 66 | 67 | 68 | # vi: sts=4 et sw=4 69 | -------------------------------------------------------------------------------- /pyxmpp/sasl/plain.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | """PLAIN authentication mechanism for PyXMPP SASL implementation. 18 | 19 | Normative reference: 20 | - `RFC 2595 `__ 21 | """ 22 | 23 | __docformat__="restructuredtext en" 24 | 25 | import logging 26 | 27 | from pyxmpp.utils import to_utf8,from_utf8 28 | from pyxmpp.sasl.core import ClientAuthenticator,ServerAuthenticator 29 | from pyxmpp.sasl.core import Success,Failure,Challenge,Response 30 | 31 | class PlainClientAuthenticator(ClientAuthenticator): 32 | """Provides PLAIN SASL authentication for a client.""" 33 | 34 | def __init__(self,password_manager): 35 | """Initialize a `PlainClientAuthenticator` object. 36 | 37 | :Parameters: 38 | - `password_manager`: name of the password manager object providing 39 | authentication credentials. 40 | :Types: 41 | - `password_manager`: `PasswordManager`""" 42 | ClientAuthenticator.__init__(self,password_manager) 43 | self.username=None 44 | self.finished=None 45 | self.password=None 46 | self.authzid=None 47 | self.__logger=logging.getLogger("pyxmpp.sasl.PlainClientAuthenticator") 48 | 49 | def start(self,username,authzid): 50 | """Start the authentication process and return the initial response. 51 | 52 | :Parameters: 53 | - `username`: username (authentication id). 54 | - `authzid`: authorization id. 55 | :Types: 56 | - `username`: `unicode` 57 | - `authzid`: `unicode` 58 | 59 | :return: the initial response or a failure indicator. 60 | :returntype: `sasl.Response` or `sasl.Failure`""" 61 | self.username=username 62 | if authzid: 63 | self.authzid=authzid 64 | else: 65 | self.authzid="" 66 | self.finished=0 67 | return self.challenge("") 68 | 69 | def challenge(self, challenge): 70 | """Process the challenge and return the response. 71 | 72 | :Parameters: 73 | - `challenge`: the challenge. 74 | :Types: 75 | - `challenge`: `str` 76 | 77 | :return: the response or a failure indicator. 78 | :returntype: `sasl.Response` or `sasl.Failure`""" 79 | _unused = challenge 80 | if self.finished: 81 | self.__logger.debug("Already authenticated") 82 | return Failure("extra-challenge") 83 | self.finished=1 84 | if self.password is None: 85 | self.password,pformat=self.password_manager.get_password(self.username) 86 | if not self.password or pformat!="plain": 87 | self.__logger.debug("Couldn't retrieve plain password") 88 | return Failure("password-unavailable") 89 | return Response("%s\000%s\000%s" % ( to_utf8(self.authzid), 90 | to_utf8(self.username), 91 | to_utf8(self.password))) 92 | 93 | def finish(self,data): 94 | """Handle authentication succes information from the server. 95 | 96 | :Parameters: 97 | - `data`: the optional additional data returned with the success. 98 | :Types: 99 | - `data`: `str` 100 | 101 | :return: a success indicator. 102 | :returntype: `Success`""" 103 | _unused = data 104 | return Success(self.username,None,self.authzid) 105 | 106 | class PlainServerAuthenticator(ServerAuthenticator): 107 | """Provides PLAIN SASL authentication for a server.""" 108 | 109 | def __init__(self,password_manager): 110 | """Initialize a `PlainServerAuthenticator` object. 111 | 112 | :Parameters: 113 | - `password_manager`: name of the password manager object providing 114 | authentication credential verification. 115 | :Types: 116 | - `password_manager`: `PasswordManager`""" 117 | ServerAuthenticator.__init__(self,password_manager) 118 | self.__logger=logging.getLogger("pyxmpp.sasl.PlainServerAuthenticator") 119 | 120 | def start(self,response): 121 | """Start the authentication process. 122 | 123 | :Parameters: 124 | - `response`: the initial response from the client. 125 | :Types: 126 | - `response`: `str` 127 | 128 | :return: a challenge, a success indicator or a failure indicator. 129 | :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 130 | if not response: 131 | return Challenge("") 132 | return self.response(response) 133 | 134 | def response(self,response): 135 | """Process a client reponse. 136 | 137 | :Parameters: 138 | - `response`: the response from the client. 139 | :Types: 140 | - `response`: `str` 141 | 142 | :return: a challenge, a success indicator or a failure indicator. 143 | :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 144 | s=response.split("\000") 145 | if len(s)!=3: 146 | self.__logger.debug("Bad response: %r" % (response,)) 147 | return Failure("not-authorized") 148 | authzid,username,password=s 149 | authzid=from_utf8(authzid) 150 | username=from_utf8(username) 151 | password=from_utf8(password) 152 | if not self.password_manager.check_password(username,password): 153 | self.__logger.debug("Bad password. Response was: %r" % (response,)) 154 | return Failure("not-authorized") 155 | info={"mechanism":"PLAIN","username":username} 156 | if self.password_manager.check_authzid(authzid,info): 157 | return Success(username,None,authzid) 158 | else: 159 | self.__logger.debug("Authzid verification failed.") 160 | return Failure("invalid-authzid") 161 | 162 | # vi: sts=4 et sw=4 163 | -------------------------------------------------------------------------------- /pyxmpp/stream.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Generic XMPP stream implementation. 19 | 20 | Normative reference: 21 | - `RFC 3920 `__ 22 | """ 23 | 24 | __docformat__="restructuredtext en" 25 | 26 | import logging 27 | 28 | from pyxmpp.streambase import StreamBase 29 | from pyxmpp.streamtls import StreamTLSMixIn 30 | from pyxmpp.streamsasl import StreamSASLMixIn 31 | 32 | class Stream(StreamTLSMixIn,StreamSASLMixIn,StreamBase): 33 | """Generic XMPP stream class. 34 | 35 | Responsible for establishing connection, parsing the stream, 36 | StartTLS encryption and SASL authentication negotiation 37 | and usage, dispatching received stanzas to apopriate handlers 38 | and sending application's stanzas. 39 | 40 | Whenever we say "stream" here we actually mean two streams 41 | (incoming and outgoing) of one connections, as defined by the XMPP 42 | specification. 43 | 44 | :Ivariables: 45 | - `lock`: RLock object used to synchronize access to Stream object. 46 | - `features`: stream features as annouced by the initiator. 47 | - `me`: local stream endpoint JID. 48 | - `peer`: remote stream endpoint JID. 49 | - `process_all_stanzas`: when `True` then all stanzas received are 50 | considered local. 51 | - `tls`: TLS connection object. 52 | - `initiator`: `True` if local stream endpoint is the initiating entity. 53 | - `_reader`: the stream reader object (push parser) for the stream. 54 | """ 55 | def __init__(self, default_ns, extra_ns = (), sasl_mechanisms = (), 56 | tls_settings = None, keepalive = 0, owner = None): 57 | """Initialize Stream object 58 | 59 | :Parameters: 60 | - `default_ns`: stream's default namespace ("jabber:client" for 61 | client, "jabber:server" for server, etc.) 62 | - `extra_ns`: sequence of extra namespace URIs to be defined for 63 | the stream. 64 | - `sasl_mechanisms`: sequence of SASL mechanisms allowed for 65 | authentication. Currently "PLAIN", "DIGEST-MD5" and "GSSAPI" are supported. 66 | - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. 67 | - `keepalive`: keepalive output interval. 0 to disable. 68 | - `owner`: `Client`, `Component` or similar object "owning" this stream. 69 | """ 70 | StreamBase.__init__(self, default_ns, extra_ns, keepalive, owner) 71 | StreamTLSMixIn.__init__(self, tls_settings) 72 | StreamSASLMixIn.__init__(self, sasl_mechanisms) 73 | self.__logger = logging.getLogger("pyxmpp.Stream") 74 | 75 | def _reset(self): 76 | """Reset `Stream` object state making it ready to handle new 77 | connections.""" 78 | StreamBase._reset(self) 79 | self._reset_tls() 80 | self._reset_sasl() 81 | 82 | def _make_stream_features(self): 83 | """Create the element for the stream. 84 | 85 | [receving entity only] 86 | 87 | :returns: new element node.""" 88 | features=StreamBase._make_stream_features(self) 89 | self._make_stream_tls_features(features) 90 | self._make_stream_sasl_features(features) 91 | return features 92 | 93 | def _process_node(self,xmlnode): 94 | """Process first level element of the stream. 95 | 96 | The element may be stream error or features, StartTLS 97 | request/response, SASL request/response or a stanza. 98 | 99 | :Parameters: 100 | - `xmlnode`: XML node describing the element 101 | """ 102 | if self._process_node_tls(xmlnode): 103 | return 104 | if self._process_node_sasl(xmlnode): 105 | return 106 | StreamBase._process_node(self,xmlnode) 107 | 108 | def _got_features(self): 109 | """Process incoming element. 110 | 111 | [initiating entity only] 112 | 113 | The received features node is available in `self.features`.""" 114 | self._handle_tls_features() 115 | self._handle_sasl_features() 116 | StreamBase._got_features(self) 117 | if not self.tls_requested and not self.authenticated: 118 | self.state_change("fully connected",self.peer) 119 | self._post_connect() 120 | 121 | # vi: sts=4 et sw=4 122 | -------------------------------------------------------------------------------- /pyxmpp/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2003-2010 Jacek Konieczny 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU Lesser General Public License Version 6 | # 2.1 as published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 16 | # 17 | 18 | """Utility functions for the pyxmpp package.""" 19 | 20 | __docformat__="restructuredtext en" 21 | 22 | import sys 23 | 24 | if sys.hexversion<0x02030000: 25 | raise ImportError,"Python 2.3 or newer is required" 26 | 27 | import time 28 | import datetime 29 | 30 | def to_utf8(s): 31 | """ 32 | Convevert `s` to UTF-8 if it is Unicode, leave unchanged 33 | if it is string or None and convert to string overwise 34 | """ 35 | if s is None: 36 | return None 37 | elif type(s) is unicode: 38 | return s.encode("utf-8") 39 | elif type(s) is str: 40 | return s 41 | else: 42 | return unicode(s).encode("utf-8") 43 | 44 | def from_utf8(s): 45 | """ 46 | Convert `s` to Unicode or leave unchanged if it is None. 47 | 48 | Regular strings are assumed to be UTF-8 encoded 49 | """ 50 | if s is None: 51 | return None 52 | elif type(s) is unicode: 53 | return s 54 | elif type(s) is str: 55 | return unicode(s,"utf-8") 56 | else: 57 | return unicode(s) 58 | 59 | minute=datetime.timedelta(minutes=1) 60 | nulldelta=datetime.timedelta() 61 | 62 | def datetime_utc_to_local(utc): 63 | """ 64 | An ugly hack to convert naive `datetime.datetime` object containing 65 | UTC time to a naive `datetime.datetime` object with local time. 66 | It seems standard Python 2.3 library doesn't provide any better way to 67 | do that. 68 | """ 69 | ts=time.time() 70 | cur=datetime.datetime.fromtimestamp(ts) 71 | cur_utc=datetime.datetime.utcfromtimestamp(ts) 72 | 73 | offset=cur-cur_utc 74 | t=utc 75 | 76 | d=datetime.timedelta(hours=2) 77 | while d>minute: 78 | local=t+offset 79 | tm=local.timetuple() 80 | tm=tm[0:8]+(0,) 81 | ts=time.mktime(tm) 82 | u=datetime.datetime.utcfromtimestamp(ts) 83 | diff=u-utc 84 | if diff-minute: 85 | break 86 | if diff>nulldelta: 87 | offset-=d 88 | else: 89 | offset+=d 90 | d/=2 91 | return local 92 | 93 | def datetime_local_to_utc(local): 94 | """ 95 | Simple function to convert naive `datetime.datetime` object containing 96 | local time to a naive `datetime.datetime` object with UTC time. 97 | """ 98 | ts=time.mktime(local.timetuple()) 99 | return datetime.datetime.utcfromtimestamp(ts) 100 | 101 | # vi: sts=4 et sw=4 102 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os.path 4 | import sys 5 | 6 | if not os.path.exists(os.path.join("pyxmpp","version.py")): 7 | print >>sys.stderr,"You need to run 'make' to use pyxmpp from SVN" 8 | sys.exit(1) 9 | 10 | execfile("build.cfg") 11 | execfile(os.path.join("pyxmpp", "version.py")) 12 | 13 | from distutils.core import setup, Extension 14 | 15 | if python_only: 16 | ext_modules = None 17 | else: 18 | # set reasonable defaults, just in case 19 | if sys.platform == 'win32': 20 | include_dirs = [r'd:\libs\include', r'd:\libs\include\libxml'] 21 | library_dirs = [r'd:\libs\lib'] 22 | else: 23 | include_dirs = ['/usr/include/libxml2','/usr/local/include/libxml2'] 24 | library_dirs = [] 25 | ext_modules = [ 26 | Extension( 27 | 'pyxmpp._xmlextra', 28 | [ 29 | 'ext/xmlextra.c', 30 | ], 31 | libraries = ['xml2'], 32 | library_dirs = library_dirs, 33 | include_dirs = include_dirs, 34 | extra_compile_args = [], 35 | ), 36 | ] 37 | 38 | 39 | #-- Let distutils do the rest 40 | setup( 41 | #-- Package description 42 | name = 'pyxmpp', 43 | version = version, 44 | description = 'XMPP/Jabber implementation for Python', 45 | author = 'Jacek Konieczny', 46 | author_email = 'jajcus@jajcus.net', 47 | download_url = 'https://github.com/downloads/Jajcus/pyxmpp/pyxmpp-{0}.tar.gz'.format(version), 48 | url = 'http://pyxmpp.jajcus.net/', 49 | classifiers = [ 50 | "Development Status :: 4 - Beta", 51 | "Intended Audience :: Developers", 52 | "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", 53 | "Operating System :: POSIX", 54 | "Programming Language :: Python", 55 | "Programming Language :: Python :: 2.6", 56 | "Programming Language :: C", 57 | "Topic :: Communications", 58 | "Topic :: Communications :: Chat", 59 | "Topic :: Internet", 60 | "Topic :: Software Development :: Libraries :: Python Modules", 61 | ], 62 | license = 'LGPL', 63 | requires = ['libxml2_python', 'dnspython(>= 1.6.0)'], 64 | 65 | ext_modules = ext_modules, 66 | 67 | #-- Python modules 68 | packages = [ 69 | 'pyxmpp', 70 | 'pyxmpp.jabber', 71 | 'pyxmpp.jabberd', 72 | 'pyxmpp.sasl', 73 | ], 74 | ) 75 | 76 | # vi: sts=4 et sw=4 77 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | tests: 4 | ./all.py -v 2 5 | -------------------------------------------------------------------------------- /tests/all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import unittest 4 | import sys 5 | import getopt 6 | 7 | all_modules=["vcard","jid","disco","imports","cache","stream_reader", "ns_operations", 8 | "message", "presence", "dataforms", "interface"] 9 | 10 | def suite(modules=None): 11 | if not modules: 12 | modules=all_modules 13 | suite = unittest.TestSuite() 14 | for mname in modules: 15 | mod=__import__(mname) 16 | suite.addTest(mod.suite()) 17 | return suite 18 | 19 | def usage(): 20 | print "Usage:" 21 | print " %s [-v ] " % (sys.argv[0],) 22 | print " %s -h" 23 | 24 | def main(args=None): 25 | verbosity=1 26 | if args: 27 | try: 28 | optlist, modules = getopt.getopt(args, 'v:h') 29 | except getopt.GetoptError: 30 | usage() 31 | sys.exit(2) 32 | for o,v in optlist: 33 | if o=='-v': 34 | verbosity=int(v) 35 | elif o=='-h': 36 | usage() 37 | sys.exit(0) 38 | else: 39 | modules=None 40 | 41 | unittest.TextTestRunner(verbosity=verbosity).run(suite()) 42 | 43 | if __name__ == '__main__': 44 | main(sys.argv[1:]) 45 | # vi: sts=4 et sw=4 46 | -------------------------------------------------------------------------------- /tests/data/disco_info_in.txt: -------------------------------------------------------------------------------- 1 | [(u'ejabberd', u'server', u'im')] 2 | [u'ejabberd:config', u'http://ejabberd.jabberstudio.org/protocol/configure', u'http://jabber.org/protocol/disco#info', u'http://jabber.org/protocol/disco#items', u'http://jabber.org/protocol/stats', u'iq', u'jabber:iq:last', u'jabber:iq:register', u'jabber:iq:time', u'jabber:iq:version', u'presence', u'presence-invisible', u'vcard-temp'] 3 | -------------------------------------------------------------------------------- /tests/data/disco_info_in.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /tests/data/disco_items_in.txt: -------------------------------------------------------------------------------- 1 | [(, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, None, None, None), (, u'Configuration', u'config', None), (, u'Online Users', u'online users', None), (, u'All Users', u'all users', None), (, u'Outgoing S2S connections', u'outgoing s2s', None), (, u'Running Nodes', u'running nodes', None), (, u'Stopped Nodes', u'stopped nodes', None)] 2 | -------------------------------------------------------------------------------- /tests/data/disco_items_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/data/disco_items_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/data/stream.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /tests/data/stream_info.txt: -------------------------------------------------------------------------------- 1 | #event offset element,attributes nested_element,attributes 2 | start 164 '' 3 | node 183 '' 4 | node 245 '' 5 | node 264 '' 6 | node 338 '\n' 7 | end 355 '' 8 | -------------------------------------------------------------------------------- /tests/data/vcard1.txt: -------------------------------------------------------------------------------- 1 | Full name: u'Peter Saint-Andre' 2 | Structural name: 3 | Family name: u'Saint-Andre' 4 | Given name: u'Peter' 5 | Middle name: u'' 6 | Prefix: u'' 7 | Suffix: u'' 8 | Nickname: u'stpeter' 9 | Birthday: u'1966-08-06' 10 | Address: 11 | Type: ['work'] 12 | POBox: '' 13 | Extended: u'Suite 600' 14 | Street: u'1899 Wynkoop Street' 15 | Locality: u'Denver' 16 | Region: u'CO' 17 | Postal code: u'80202' 18 | Country: u'USA' 19 | Address: 20 | Type: ['home'] 21 | POBox: '' 22 | Extended: u'' 23 | Street: u'' 24 | Locality: u'Denver' 25 | Region: u'CO' 26 | Postal code: u'80209' 27 | Country: u'USA' 28 | Telephone: 29 | Type: ['voice', 'work'] 30 | Number: u'303-308-3282' 31 | Telephone: 32 | Type: ['voice', 'home'] 33 | Number: u'303-555-1212' 34 | E-mail: 35 | Type: ['internet'] 36 | Address: u'stpeter@jabber.org' 37 | JID: 38 | Title: u'Executive Director' 39 | Role: u'Patron Saint' 40 | Organization: 41 | Name: u'Jabber Software Foundation' 42 | Unit: u'' 43 | URL: u'http://www.jabber.org/people/stpeter.php' 44 | Description: u'More information about me is located on my\n personal website: http://www.saint-andre.com/' 45 | -------------------------------------------------------------------------------- /tests/data/vcard1.xml: -------------------------------------------------------------------------------- 1 | 2 | Peter Saint-Andre 3 | 4 | Saint-Andre 5 | Peter 6 | 7 | 8 | stpeter 9 | http://www.jabber.org/people/stpeter.php 10 | 1966-08-06 11 | 12 | Jabber Software Foundation 13 | 14 | 15 | Executive Director 16 | Patron Saint 17 | 303-308-3282 18 | 19 | 20 | 21 | 22 | Suite 600 23 | 1899 Wynkoop Street 24 | Denver 25 | CO 26 | 80202 27 | USA 28 | 29 | 303-555-1212 30 | 31 | 32 | 33 | 34 | 35 | 36 | Denver 37 | CO 38 | 80209 39 | USA 40 | 41 | stpeter@jabber.org 42 | stpeter@jabber.org 43 | 44 | More information about me is located on my 45 | personal website: http://www.saint-andre.com/ 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/data/vcard2.txt: -------------------------------------------------------------------------------- 1 | Full name: u'Bj=F8rn Jensen' 2 | Structural name: 3 | Family name: u'Jensen' 4 | Given name: u'Bj=F8rn' 5 | Middle name: u'' 6 | Prefix: u'' 7 | Suffix: u'' 8 | Telephone: 9 | Type: [u'work', u'voice', u'msg'] 10 | Number: u'+1 313 747-4454' 11 | E-mail: 12 | Type: [u'internet'] 13 | Address: u'bjorn@umich.edu' 14 | Key: 15 | Type: u'x509' 16 | Value: u'dGhpcyBjb3VsZCBiZSAKbXkgY2VydGlmaWNhdGUK' 17 | -------------------------------------------------------------------------------- /tests/data/vcard2.vcf: -------------------------------------------------------------------------------- 1 | 2 | begin:VCARD 3 | source:ldap://cn=bjorn%20Jensen, o=university%20of%20Michigan, c=US 4 | name:Bjorn Jensen 5 | fn:Bj=F8rn Jensen 6 | n:Jensen;Bj=F8rn 7 | email;type=internet:bjorn@umich.edu 8 | tel;type=work,voice,msg:+1 313 747-4454 9 | key;type=x509;encoding=B:dGhpcyBjb3VsZCBiZSAKbXkgY2VydGlmaWNhdGUK 10 | end:VCARD 11 | -------------------------------------------------------------------------------- /tests/data/vcard3.txt: -------------------------------------------------------------------------------- 1 | Full name: u'Mi\u015b Uszatek' 2 | Structural name: 3 | Family name: u'Uszatek' 4 | Given name: u'Mi\u015b' 5 | Middle name: u'' 6 | Prefix: u'' 7 | Suffix: u'' 8 | Nickname: u'Misio' 9 | Birthday: u'1977-02-07T00:00:00Z' 10 | Telephone: 11 | Type: [u'home'] 12 | Number: u'+99 12 3456789' 13 | Telephone: 14 | Type: [u'work'] 15 | Number: u'+99 98 7654321' 16 | Telephone: 17 | Type: [u'cell'] 18 | Number: u'+99 123123123' 19 | E-mail: 20 | Type: [u'pref'] 21 | Address: u'mis.uszatek@example.com' 22 | Role: u'Nudziarz' 23 | Organization: 24 | Name: u'Wieczorynka' 25 | Unit: None 26 | Categories: [u'Friend'] 27 | User id: u'Favdn6ur8U' 28 | URL: u'http://example.com/' 29 | Class: u'PUBLIC' 30 | -------------------------------------------------------------------------------- /tests/data/vcard3.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | BDAY:1977-02-07T00:00:00Z 3 | CATEGORIES:Friend 4 | CLASS:PUBLIC 5 | EMAIL;TYPE=PREF:mis.uszatek@example.com 6 | FN:Miś Uszatek 7 | N:Uszatek;Miś;;; 8 | NICKNAME:Misio 9 | ORG:Wieczorynka 10 | ROLE:Nudziarz 11 | TEL;TYPE=HOME:+99 12 3456789 12 | TEL;TYPE=WORK:+99 98 7654321 13 | TEL;TYPE=CELL:+99 123123123 14 | UID:Favdn6ur8U 15 | URL:http://example.com/ 16 | VERSION:3.0 17 | X-KADDRESSBOOK-X-IMAddress:miś@jabber.example.com 18 | X-KADDRESSBOOK-X-SpousesName:Misia 19 | END:VCARD 20 | 21 | -------------------------------------------------------------------------------- /tests/data/vcard_with_semicolon.xml: -------------------------------------------------------------------------------- 1 | 2 | Peter Saint-Andre; the XMPP guru 3 | 4 | Saint-Andre; the XMPP guru 5 | Peter 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/data/vcard_without_fn.txt: -------------------------------------------------------------------------------- 1 | Full name: u'Peter Saint-Andre' 2 | Structural name: 3 | Family name: u'Saint-Andre' 4 | Given name: u'Peter' 5 | Middle name: u'' 6 | Prefix: u'' 7 | Suffix: u'' 8 | -------------------------------------------------------------------------------- /tests/data/vcard_without_fn.xml: -------------------------------------------------------------------------------- 1 | 2 | Peter Saint-Andre 3 | 4 | -------------------------------------------------------------------------------- /tests/data/vcard_without_n.txt: -------------------------------------------------------------------------------- 1 | Full name: u'Peter Saint-Andre' 2 | Structural name: 3 | Family name: u'Saint-Andre' 4 | Given name: u'Peter' 5 | Middle name: u'' 6 | Prefix: u'' 7 | Suffix: u'' 8 | -------------------------------------------------------------------------------- /tests/data/vcard_without_n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Saint-Andre 4 | Peter 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/disco.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | import libxml2 6 | from pyxmpp.jabber import disco 7 | from pyxmpp.jid import JID 8 | 9 | test_identities=[ 10 | (u"Test",u"category",u"type"), 11 | (u"Test2",u"category2",u"type"), 12 | (u"Test3",u"category",u"type2"), 13 | (u"Teścik",u"ółńść",u"źółńś"), 14 | ]; 15 | test_identities.sort() 16 | 17 | notest_identities=[ 18 | (u"category",u"another-type"), 19 | (u"category3",u"type"), 20 | (u"category4",None), 21 | ]; 22 | 23 | test_features=[ 24 | u"test-feature", 25 | u"http://jabber.org/protocol/disco#info", 26 | # u"http://dżabber.example.com/example-namespace", 27 | ]; 28 | test_features.sort() 29 | 30 | notest_features=[ 31 | u"another-test-feature", 32 | u"http://jabber.org/protocol/disco#items", 33 | u"http://dżabber.example.com/another-example-namespace", 34 | ]; 35 | 36 | 37 | class TestDiscoInfo(unittest.TestCase): 38 | def test_xml_input(self): 39 | xmldata=libxml2.parseFile("data/disco_info_in.xml") 40 | di=disco.DiscoInfo(xmldata.getRootElement()) 41 | txt=`[(i.name,i.category,i.type) for i in di.identities]` 42 | txt+="\n"+`di.features`+"\n" 43 | should_be=file("data/disco_info_in.txt").read() 44 | self.failUnlessEqual(txt,should_be) 45 | 46 | def build_disco_info(self,node=None): 47 | di=disco.DiscoInfo(node) 48 | for name,category,type in test_identities: 49 | di.add_identity(name,category,type) 50 | for var in test_features: 51 | di.add_feature(var) 52 | return di 53 | 54 | # def test_xml_output(self): 55 | 56 | def test_building(self): 57 | self.build_disco_info() 58 | 59 | def test_building_with_node(self): 60 | di=self.build_disco_info("test") 61 | self.failUnlessEqual(di.node,"test") 62 | 63 | def test_identities(self): 64 | di=self.build_disco_info() 65 | actual_identities=[(i.name,i.category,i.type) for i in di.identities] 66 | actual_identities.sort() 67 | self.failUnlessEqual(actual_identities,test_identities) 68 | 69 | def test_features(self): 70 | di=self.build_disco_info() 71 | actual_features=di.get_features() 72 | actual_features.sort() 73 | self.failUnlessEqual(actual_features,test_features) 74 | 75 | def test_identity_is(self): 76 | di=self.build_disco_info() 77 | for name,category,type in test_identities: 78 | self.failUnless(di.identity_is(category,type), 79 | "Identity (%r,%r) not matched" % (category,type)) 80 | self.failUnless(di.identity_is(category,None), 81 | "Identity (%r,%r) not matched" % (category,None)) 82 | for category,type in notest_identities: 83 | self.failIf(di.identity_is(category,type), 84 | "Identity (%r,%r) matched" % (category,type)) 85 | 86 | def test_has_feature(self): 87 | di=self.build_disco_info() 88 | for var in test_features: 89 | self.failUnless(di.has_feature(var),"Feature %r not found" % (var,)) 90 | for var in notest_features: 91 | self.failIf(di.has_feature(var),"Feature %r found" % (var,)) 92 | 93 | 94 | # def test_building(self): 95 | 96 | 97 | test_items=[ 98 | (JID(u"a@b.c"),None,None), 99 | (JID(u"a@b.c"),u"d",None), 100 | (JID(u"f@b.c"),None,u"e"), 101 | (JID(u"f@b.c"),u"d",u"e"), 102 | (JID(u"użytkownik@dżabber"),u"węzeł",u"Teścik"), 103 | ]; 104 | test_items.sort() 105 | 106 | notest_items=[ 107 | (JID(u"test@example.com"),None), 108 | (JID(u"test@example.com"),u"d"), 109 | (JID(u"test@example.com"),u"test"), 110 | (JID(u"a@b.c"),u"test"), 111 | (JID(u"użytkownik2@dżabber"),u"węzeł"), 112 | ]; 113 | 114 | class TestDiscoItems(unittest.TestCase): 115 | def test_xml_input(self): 116 | xmldata=libxml2.parseFile("data/disco_items_in.xml") 117 | di=disco.DiscoItems(xmldata.getRootElement()) 118 | txt=`[(i.jid,i.name,i.node,i.action) for i in di.items]`+"\n" 119 | should_be=file("data/disco_items_in.txt").read() 120 | self.failUnlessEqual(txt,should_be) 121 | 122 | def build_disco_items(self,node=None): 123 | di=disco.DiscoItems(node) 124 | for jid,node,name in test_items: 125 | di.add_item(jid,node,name) 126 | return di 127 | 128 | def test_xml_output(self): 129 | di=self.build_disco_items() 130 | txt=di.as_xml().serialize() 131 | should_be=file("data/disco_items_out.xml").read() 132 | self.failUnlessEqual(txt,should_be) 133 | 134 | def test_building(self): 135 | self.build_disco_items() 136 | 137 | def test_building_with_node(self): 138 | di=self.build_disco_items("test") 139 | self.failUnlessEqual(di.node,"test") 140 | 141 | def test_items(self): 142 | di=self.build_disco_items() 143 | actual_items=[(i.jid,i.node,i.name) for i in di.items] 144 | actual_items.sort() 145 | self.failUnlessEqual(actual_items,test_items) 146 | 147 | def test_has_item(self): 148 | di=self.build_disco_items() 149 | for jid,node,name in test_items: 150 | self.failUnless(di.has_item(jid,node),"Item (%r,%r) not found" % (jid,node)) 151 | for jid,node in notest_items: 152 | self.failIf(di.has_item(jid,node),"Item (%r,%r) found" % (jid,node)) 153 | 154 | def suite(): 155 | suite = unittest.TestSuite() 156 | suite.addTest(unittest.makeSuite(TestDiscoInfo)) 157 | suite.addTest(unittest.makeSuite(TestDiscoItems)) 158 | return suite 159 | 160 | if __name__ == '__main__': 161 | unittest.TextTestRunner(verbosity=2).run(suite()) 162 | 163 | # vi: sts=4 et sw=4 164 | -------------------------------------------------------------------------------- /tests/imports.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | 6 | import pyxmpp.all as pyxmpp 7 | import pyxmpp.jabber.all 8 | import pyxmpp.jabberd.all 9 | 10 | class TestImports(unittest.TestCase): 11 | def test_Stream(self): 12 | import pyxmpp.stream as m2 13 | self.failUnless(pyxmpp.Stream is m2.Stream,"Stream not imported correctly") 14 | def test_Stream_from(self): 15 | from pyxmpp import Stream as n1 16 | import pyxmpp.stream as m2 17 | self.failUnless(n1 is m2.Stream,"Stream not imported correctly") 18 | def test_JID(self): 19 | import pyxmpp.jid as m2 20 | self.failUnless(pyxmpp.JID is m2.JID,"JID not imported correctly") 21 | def test_JID_from(self): 22 | from pyxmpp import JID as n1 23 | import pyxmpp.jid as m2 24 | self.failUnless(n1 is m2.JID,"JID not imported correctly") 25 | def test_JabberClient(self): 26 | import pyxmpp.jabber.client as m2 27 | self.failUnless(pyxmpp.jabber.Client is m2.JabberClient,"JabberClient not imported correctly") 28 | def test_JabberClient_from(self): 29 | from pyxmpp.jabber import Client as n1 30 | import pyxmpp.jabber.client as m2 31 | self.failUnless(n1 is m2.JabberClient,"JabberClient not imported correctly") 32 | def test_Component(self): 33 | import pyxmpp.jabberd.component as m2 34 | self.failUnless(pyxmpp.jabberd.Component is m2.Component,"Component not imported correctly") 35 | def test_Component_from(self): 36 | from pyxmpp.jabberd import Component as n1 37 | import pyxmpp.jabberd.component as m2 38 | self.failUnless(n1 is m2.Component,"Component not imported correctly") 39 | 40 | def suite(): 41 | suite = unittest.TestSuite() 42 | suite.addTest(unittest.makeSuite(TestImports)) 43 | return suite 44 | 45 | if __name__ == '__main__': 46 | unittest.TextTestRunner(verbosity=2).run(suite()) 47 | 48 | # vi: sts=4 et sw=4 49 | -------------------------------------------------------------------------------- /tests/interface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | 6 | import pyxmpp.interface 7 | import pyxmpp.interface_micro_impl 8 | 9 | try: 10 | import zope.interface 11 | zope_interface_found = True 12 | except ImportError: 13 | zope_interface_found = False 14 | 15 | class TestInterface(unittest.TestCase): 16 | interfaces_implementation = None 17 | 18 | def test_interface_definitions(self): 19 | class I1(self.interfaces_implementation.Interface): 20 | pass 21 | class I2(self.interfaces_implementation.Interface): 22 | a = self.interfaces_implementation.Attribute("some attribute") 23 | class I3(self.interfaces_implementation.Interface): 24 | def f(arg1, arg2): 25 | """some funtion""" 26 | class I3(self.interfaces_implementation.Interface): 27 | a = self.interfaces_implementation.Attribute("some attribute") 28 | def f(arg1, arg2): 29 | """some funtion""" 30 | 31 | def test_implementedBy(self): 32 | class I1(self.interfaces_implementation.Interface): 33 | pass 34 | class I2(self.interfaces_implementation.Interface): 35 | pass 36 | class C1(object): 37 | self.interfaces_implementation.implements(I1) 38 | class C2(object): 39 | self.interfaces_implementation.implements(I2) 40 | self.failUnless(I1.implementedBy(C1)) 41 | self.failUnless(I2.implementedBy(C2)) 42 | self.failIf(I2.implementedBy(C1)) 43 | self.failIf(I1.implementedBy(C2)) 44 | 45 | def test_providedBy(self): 46 | class I1(self.interfaces_implementation.Interface): 47 | pass 48 | class I2(self.interfaces_implementation.Interface): 49 | pass 50 | class C1(object): 51 | self.interfaces_implementation.implements(I1) 52 | class C2(object): 53 | self.interfaces_implementation.implements(I2) 54 | o1=C1() 55 | o2=C2() 56 | self.failUnless(I1.providedBy(o1)) 57 | self.failUnless(I2.providedBy(o2)) 58 | self.failIf(I2.providedBy(o1)) 59 | self.failIf(I1.providedBy(o2)) 60 | 61 | def test_inheritance(self): 62 | class I1(self.interfaces_implementation.Interface): 63 | pass 64 | class I2(I1): 65 | pass 66 | class C1(object): 67 | self.interfaces_implementation.implements(I2) 68 | o1 = C1() 69 | self.failUnless(I1.providedBy(o1)) 70 | self.failUnless(issubclass(I2, I1)) 71 | 72 | class TestPyXMPPInterface(TestInterface): 73 | interfaces_implementation = pyxmpp.interface 74 | 75 | class TestPyXMPPMicroInterface(TestInterface): 76 | interfaces_implementation = pyxmpp.interface_micro_impl 77 | 78 | if zope_interface_found: 79 | class TestZopeInterface(TestInterface): 80 | interfaces_implementation = zope.interface 81 | 82 | class TestZopeAndPyXMPPInterface(unittest.TestCase): 83 | def test_interface_identity(self): 84 | self.failUnless(pyxmpp.interface.Interface is zope.interface.Interface) 85 | def test_attribute_identity(self): 86 | self.failUnless(pyxmpp.interface.Attribute is zope.interface.Attribute) 87 | def test_implements_identity(self): 88 | self.failUnless(pyxmpp.interface.implements is zope.interface.implements) 89 | 90 | def suite(): 91 | suite = unittest.TestSuite() 92 | suite.addTest(unittest.makeSuite(TestPyXMPPInterface)) 93 | suite.addTest(unittest.makeSuite(TestPyXMPPMicroInterface)) 94 | if zope_interface_found: 95 | suite.addTest(unittest.makeSuite(TestZopeInterface)) 96 | suite.addTest(unittest.makeSuite(TestZopeAndPyXMPPInterface)) 97 | return suite 98 | 99 | if __name__ == '__main__': 100 | unittest.TextTestRunner(verbosity=2).run(suite()) 101 | 102 | # vi: sts=4 et sw=4 103 | -------------------------------------------------------------------------------- /tests/jid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | from pyxmpp.jid import JID,JIDError 6 | from pyxmpp import xmppstringprep 7 | 8 | valid_jids=[ 9 | (u"a@b/c", 10 | (u"a",u"b",u"c")), 11 | (u"example.com", 12 | (None,u"example.com",None)), 13 | (u"example.com/Test", 14 | (None,u"example.com","Test")), 15 | (u"jajcus@jabber.bnet.pl", 16 | (u"jajcus",u"jabber.bnet.pl",None)), 17 | (u"jajcus@jabber.bnet.pl/Test", 18 | (u"jajcus",u"jabber.bnet.pl",u"Test")), 19 | (u"Jajcus@jaBBer.bnet.pl/Test", 20 | (u"jajcus",u"jabber.bnet.pl",u"Test")), 21 | (u"Jajcus@jaBBer.bnet.pl/test", 22 | (u"jajcus",u"jabber.bnet.pl",u"test")), 23 | (u"jajcuś@dżabber.example.com/Test", 24 | (u"jajcuś",u"dżabber.example.com",u"Test")), 25 | (u"JAJCUŚ@DŻABBER.EXAMPLE.COM/TEST", 26 | (u"jajcuś",u"dżabber.example.com",u"TEST")), 27 | (u"%s@%s/%s" % (u"x"*1023,u"x"*1023,u"x"*1023), 28 | (u"x"*1023,u"x"*1023,u"x"*1023)), 29 | ] 30 | 31 | valid_tuples=[ 32 | ((u"a",u"b",u"c"),u"a@b/c"), 33 | ((None,u"example.com",None),u"example.com"), 34 | ((u"",u"example.com",u""),u"example.com"), 35 | ((None,u"example.com","Test"),u"example.com/Test"), 36 | ((u"jajcus",u"jabber.bnet.pl",None),u"jajcus@jabber.bnet.pl"), 37 | ((u"jajcus",u"jabber.bnet.pl",u"Test"),u"jajcus@jabber.bnet.pl/Test"), 38 | ((u"Jajcus",u"jaBBer.bnet.pl",u"Test"),u"jajcus@jabber.bnet.pl/Test"), 39 | ((u"Jajcus",u"jaBBer.bnet.pl",u"test"),u"jajcus@jabber.bnet.pl/test"), 40 | ((u"jajcuś",u"dżabber.example.com",u"Test"),u"jajcuś@dżabber.example.com/Test"), 41 | ((u"JAJCUŚ",u"DŻABBER.EXAMPLE.COM",u"TEST"),u"jajcuś@dżabber.example.com/TEST"), 42 | ] 43 | 44 | invalid_jids=[ 45 | u"/Test", 46 | u"#@$%#^$%#^&^$", 47 | u"<>@example.com", 48 | u"test@example.com/(&*&^%$#@@!#", 49 | u"\01\02\05@example.com", 50 | u"test@\01\02\05", 51 | u"test@example.com/\01\02\05", 52 | u"%s@%s/%s" % (u"x"*1024,u"x"*1023,u"x"*1023), 53 | u"%s@%s/%s" % (u"x"*1023,u"x"*1024,u"x"*1023), 54 | u"%s@%s/%s" % (u"x"*1023,u"x"*1023,u"x"*1024), 55 | u"%só@%s/%s" % (u"x"*1022,u"x"*1023,u"x"*1023), 56 | u"%s@%só/%s" % (u"x"*1023,u"x"*1022,u"x"*1023), 57 | u"%s@%s/%só" % (u"x"*1023,u"x"*1023,u"x"*1022), 58 | ] 59 | 60 | comparisions_true=[ 61 | 'JID(u"a@b.c") == JID(u"a@b.c")', 62 | 'JID(u"a@b.c") == JID(u"A@b.c")', 63 | 'JID(u"a@b.c") != JID(u"b@b.c")', 64 | 'JID(u"a@b.c") < JID(u"b@b.c")', 65 | 'JID(u"b@b.c") > JID(u"a@b.c")', 66 | 'JID(u"a@b.c") > None', 67 | 'JID(u"1@b.c") > None', 68 | 'None < JID(u"1@b.c")', 69 | ] 70 | 71 | comparisions_false=[ 72 | 'JID(u"a@b.c") != JID(u"a@b.c")', 73 | 'JID(u"a@b.c") != JID(u"A@b.c")', 74 | 'JID(u"a@b.c") == JID(u"b@b.c")', 75 | 'JID(u"a@b.c") > JID(u"b@b.c")', 76 | 'JID(u"b@b.c") < JID(u"a@b.c")', 77 | 'JID(u"a@b.c") < None', 78 | 'JID(u"1@b.c") < None', 79 | 'None > JID(u"1@b.c")', 80 | ] 81 | 82 | class TestJID(unittest.TestCase): 83 | def test_jid_from_string(self): 84 | for jid,tuple in valid_jids: 85 | j=JID(jid) 86 | jtuple=(j.node,j.domain,j.resource) 87 | self.failUnlessEqual(jtuple,tuple) 88 | def test_jid_from_tuple(self): 89 | for (node,domain,resource),jid in valid_tuples: 90 | j=JID(node,domain,resource) 91 | self.failUnlessEqual(unicode(j),jid) 92 | def test_invalid_jids(self): 93 | for jid in invalid_jids: 94 | try: 95 | j=JID(jid) 96 | except JIDError,e: 97 | return 98 | except Exception,e: 99 | raise 100 | self.fail("Invalid JID passed: %r -> %r" % (jid,j)) 101 | def test_comparision(self): 102 | for e in comparisions_true: 103 | result=eval(e) 104 | self.failUnless(result,'Expression %r gave: %r' % (e,result)) 105 | for e in comparisions_false: 106 | result=eval(e) 107 | self.failIf(result,'Expression %r gave: %r' % (e,result)) 108 | 109 | class TestUncachedJID(TestJID): 110 | def setUp(self): 111 | import weakref 112 | JID.cache=weakref.WeakValueDictionary() 113 | self.saved_stringprep_cache_size=xmppstringprep.stringprep_cache_size 114 | xmppstringprep.set_stringprep_cache_size(0) 115 | def tearDown(self): 116 | xmppstringprep.set_stringprep_cache_size(self.saved_stringprep_cache_size) 117 | 118 | def suite(): 119 | suite = unittest.TestSuite() 120 | suite.addTest(unittest.makeSuite(TestUncachedJID)) 121 | suite.addTest(unittest.makeSuite(TestJID)) 122 | return suite 123 | 124 | if __name__ == '__main__': 125 | unittest.TextTestRunner(verbosity=2).run(suite()) 126 | 127 | # vi: sts=4 et sw=4 128 | -------------------------------------------------------------------------------- /tests/message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | import libxml2 6 | from pyxmpp.message import Message 7 | from pyxmpp.jid import JID 8 | 9 | message1 = """ 10 | 11 | Subject 12 | The body 13 | thread-id 14 | 15 | """ 16 | 17 | message1_doc = libxml2.parseDoc(message1) 18 | message1_node = message1_doc.getRootElement() 19 | 20 | message2 = """""" 21 | 22 | message2_doc = libxml2.parseDoc(message2) 23 | message2_node = message2_doc.getRootElement() 24 | 25 | class TestMessage(unittest.TestCase): 26 | def check_message_full(self, m): 27 | self.failUnlessEqual(m.get_from(), JID("source@example.com/res")) 28 | self.failUnlessEqual(m.get_to(), JID("dest@example.com")) 29 | self.failUnlessEqual(m.get_type(), "normal") 30 | self.failUnlessEqual(m.get_id(), "1") 31 | self.failUnlessEqual(m.get_subject(), u"Subject") 32 | self.failUnlessEqual(m.get_body(), u"The body") 33 | self.failUnlessEqual(m.get_thread(), u"thread-id") 34 | nodes = m.xpath_eval("t:payload", {"t": "http://pyxmpp.jajcus.net/xmlns/test"}) 35 | self.failUnless(nodes) 36 | self.failUnlessEqual(nodes[0].name, "payload") 37 | self.failUnless(nodes[0].children) 38 | self.failUnlessEqual(nodes[0].children.name, "abc") 39 | 40 | def check_message_empty(self, m): 41 | self.failUnlessEqual(m.get_from(), None) 42 | self.failUnlessEqual(m.get_to(), None) 43 | self.failUnlessEqual(m.get_type(), None) 44 | self.failUnlessEqual(m.get_id(), None) 45 | self.failUnlessEqual(m.get_subject(), None) 46 | self.failUnlessEqual(m.get_body(), None) 47 | self.failUnlessEqual(m.get_thread(), None) 48 | nodes = m.xpath_eval("t:payload",{"t":"http://pyxmpp.jajcus.net/xmlns/test"}) 49 | self.failIf(nodes) 50 | 51 | def test_message_full_from_xml(self): 52 | m = Message(message1_node) 53 | self.check_message_full(m) 54 | 55 | def test_message_empty_from_xml(self): 56 | m = Message(message2_node) 57 | self.check_message_empty(m) 58 | 59 | def test_message_empty(self): 60 | m = Message() 61 | self.check_message_empty(m) 62 | node, doc = self.stanza_to_xml(m) 63 | self.check_message_empty( Message(node) ) 64 | node, doc = self.xml_to_xml(doc) 65 | self.check_message_empty( Message(node) ) 66 | 67 | def test_message_full(self): 68 | m = Message( 69 | from_jid = JID("source@example.com/res"), 70 | to_jid = JID("dest@example.com"), 71 | stanza_type = "normal", 72 | stanza_id = u"1", 73 | subject = u"Subject", 74 | body = u"The body", 75 | thread = u"thread-id") 76 | n = m.xmlnode.newChild(None, "payload", None) 77 | ns = n.newNs("http://pyxmpp.jajcus.net/xmlns/test", "t") 78 | n.setNs(ns) 79 | n.newChild(ns, "abc", None) 80 | self.check_message_full( m ) 81 | node, doc = self.stanza_to_xml(m) 82 | self.check_message_full( Message(node) ) 83 | xml = self.xml_to_xml(doc) 84 | self.check_message_full( Message(node) ) 85 | 86 | def stanza_to_xml(self, stanza): 87 | d = libxml2.newDoc("1.0") 88 | r = d.newChild(None, "root", None) 89 | ns = r.newNs("jabber:server", None) 90 | r.setNs(ns) 91 | d.setRootElement(r) 92 | xml = stanza.xmlnode.docCopyNode(d, 1) 93 | r.addChild(xml) 94 | return xml,d 95 | 96 | def xml_to_xml(self, xml): 97 | d = libxml2.parseDoc(xml.serialize()) 98 | r = d.getRootElement() 99 | xml = r.children 100 | return xml, d 101 | 102 | def suite(): 103 | suite = unittest.TestSuite() 104 | suite.addTest(unittest.makeSuite(TestMessage)) 105 | return suite 106 | 107 | if __name__ == '__main__': 108 | unittest.TextTestRunner(verbosity=2).run(suite()) 109 | 110 | # vi: sts=4 et sw=4 111 | -------------------------------------------------------------------------------- /tests/ns_operations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | import libxml2 6 | from pyxmpp import xmlextra 7 | from stream_reader import xml_elements_equal 8 | 9 | input_xml = """ 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | """ 21 | input_doc = libxml2.parseDoc(input_xml) 22 | input_root = input_doc.getRootElement() 23 | 24 | input_xml2 = """ 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | """ 36 | input_doc2 = libxml2.parseDoc(input_xml2) 37 | input_root2 = input_doc2.getRootElement() 38 | 39 | 40 | output_xml = """ 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | """ 52 | output_doc = libxml2.parseDoc(output_xml) 53 | output_root = output_doc.getRootElement() 54 | 55 | 56 | class TestReplaceNs(unittest.TestCase): 57 | def test_replace_ns(self): 58 | doc = libxml2.newDoc("1.0") 59 | 60 | root = doc.newChild(None, "root", None) 61 | common_ns = root.newNs(xmlextra.COMMON_NS, None) 62 | root.setNs(common_ns) 63 | doc.setRootElement(root) 64 | 65 | n = input_doc.getRootElement() 66 | try: 67 | input_ns = n.ns() 68 | except libxml2.treeError: 69 | input_ns = None 70 | n = n.children 71 | while n: 72 | n1 = n.docCopyNode(doc, 1) 73 | root.addChild(n1) 74 | if n1.type == 'element': 75 | try: 76 | n1_ns = n1.ns() 77 | except libxml2.treeError: 78 | ns1_ns = None 79 | if n1_ns.content == input_ns.content: 80 | xmlextra.replace_ns(n1, n1_ns, common_ns) 81 | n = n.next 82 | self.failUnless(xml_elements_equal(root, output_root)) 83 | 84 | def test_replace_null_ns(self): 85 | doc = libxml2.newDoc("1.0") 86 | 87 | root = doc.newChild(None, "root", None) 88 | common_ns = root.newNs(xmlextra.COMMON_NS, None) 89 | root.setNs(common_ns) 90 | doc.setRootElement(root) 91 | 92 | n = input_doc2.getRootElement() 93 | try: 94 | input_ns = n.ns() 95 | except libxml2.treeError: 96 | input_ns = None 97 | n = n.children 98 | while n: 99 | n1 = n.docCopyNode(doc, 1) 100 | root.addChild(n1) 101 | if n1.type == 'element': 102 | try: 103 | n1_ns = n1.ns() 104 | except libxml2.treeError: 105 | n1_ns = None 106 | if n1_ns is None: 107 | xmlextra.replace_ns(n1, n1_ns, common_ns) 108 | n = n.next 109 | self.failUnless(xml_elements_equal(root, output_root)) 110 | 111 | def test_safe_serialize(self): 112 | s1 = """""" 113 | doc1 = libxml2.parseDoc(s1) 114 | root1 = doc1.getRootElement() 115 | el1 = root1.children 116 | try: 117 | root1_ns = root1.ns() 118 | except libxml2.treeError: 119 | root1_ns = None 120 | el1.setNs(root1_ns) 121 | 122 | #s = el1.serialize() 123 | s = xmlextra.safe_serialize(el1) 124 | 125 | s2 = '%s' % (s,) 126 | 127 | doc2 = libxml2.parseDoc(s2) 128 | root2 = doc2.getRootElement() 129 | self.failUnless(xml_elements_equal(root1, root2)) 130 | 131 | 132 | def suite(): 133 | suite = unittest.TestSuite() 134 | suite.addTest(unittest.makeSuite(TestReplaceNs)) 135 | return suite 136 | 137 | if __name__ == '__main__': 138 | unittest.TextTestRunner(verbosity=2).run(suite()) 139 | 140 | # vi: sts=4 et sw=4 141 | -------------------------------------------------------------------------------- /tests/presence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | import libxml2 6 | from pyxmpp.presence import Presence 7 | from pyxmpp.jid import JID 8 | 9 | presence1 = """ 10 | 11 | away 12 | The Status 13 | 10 14 | 15 | """ 16 | presence1_doc = libxml2.parseDoc(presence1) 17 | presence1_node = presence1_doc.getRootElement() 18 | 19 | presence2 = """""" 20 | presence2_doc = libxml2.parseDoc(presence2) 21 | presence2_node = presence2_doc.getRootElement() 22 | 23 | presence3 = """""" 24 | presence3_doc = libxml2.parseDoc(presence3) 25 | presence3_node = presence3_doc.getRootElement() 26 | 27 | class TestPresence(unittest.TestCase): 28 | def check_presence_full(self, p): 29 | self.failUnlessEqual(p.get_from(), JID("source@example.com/res")) 30 | self.failUnlessEqual(p.get_to(), JID("dest@example.com")) 31 | self.failUnlessEqual(p.get_type(), None) 32 | self.failUnlessEqual(p.get_id(), "1") 33 | self.failUnlessEqual(p.get_show(), "away") 34 | self.failUnlessEqual(p.get_status(), "The Status") 35 | self.failUnlessEqual(p.get_priority(), 10) 36 | nodes = p.xpath_eval("t:payload", {"t": "http://pyxmpp.jajcus.net/xmlns/test"}) 37 | self.failUnless(nodes) 38 | self.failUnlessEqual(nodes[0].name, "payload") 39 | self.failUnless(nodes[0].children) 40 | self.failUnlessEqual(nodes[0].children.name, "abc") 41 | 42 | def check_presence_empty(self, p): 43 | self.failUnlessEqual(p.get_from(), None) 44 | self.failUnlessEqual(p.get_to(), None) 45 | self.failUnlessEqual(p.get_type(), None) 46 | self.failUnlessEqual(p.get_id(), None) 47 | self.failUnlessEqual(p.get_show(), None) 48 | self.failUnlessEqual(p.get_status(), None) 49 | self.failUnlessEqual(p.get_priority(), 0) 50 | nodes = p.xpath_eval("t:payload",{"t":"http://pyxmpp.jajcus.net/xmlns/test"}) 51 | self.failIf(nodes) 52 | 53 | def check_presence_subscribe(self, p): 54 | self.failUnlessEqual(p.get_from(), JID("source@example.com/res")) 55 | self.failUnlessEqual(p.get_to(), JID("dest@example.com")) 56 | self.failUnlessEqual(p.get_type(), "subscribe") 57 | self.failUnlessEqual(p.get_id(), None) 58 | self.failUnlessEqual(p.get_show(), None) 59 | self.failUnlessEqual(p.get_status(), None) 60 | 61 | def test_presence_full_from_xml(self): 62 | p = Presence(presence1_node) 63 | self.check_presence_full(p) 64 | 65 | def test_presence_empty_from_xml(self): 66 | p = Presence(presence2_node) 67 | self.check_presence_empty(p) 68 | 69 | def test_presence_subscribe_from_xml(self): 70 | p = Presence(presence3_node) 71 | self.check_presence_subscribe(p) 72 | 73 | def test_presence_empty(self): 74 | p = Presence() 75 | self.check_presence_empty(p) 76 | node, doc = self.stanza_to_xml(p) 77 | self.check_presence_empty( Presence(node) ) 78 | node, doc = self.xml_to_xml(doc) 79 | self.check_presence_empty( Presence(node) ) 80 | 81 | def test_presence_full(self): 82 | p = Presence( 83 | from_jid = JID("source@example.com/res"), 84 | to_jid = JID("dest@example.com"), 85 | stanza_type = None, 86 | stanza_id = u"1", 87 | show = u"away", 88 | status = u"The Status", 89 | priority = "10") 90 | n = p.xmlnode.newChild(None, "payload", None) 91 | ns = n.newNs("http://pyxmpp.jajcus.net/xmlns/test", "t") 92 | n.setNs(ns) 93 | n.newChild(ns, "abc", None) 94 | self.check_presence_full(p) 95 | node, doc = self.stanza_to_xml(p) 96 | self.check_presence_full( Presence(node) ) 97 | xml = self.xml_to_xml(doc) 98 | self.check_presence_full( Presence(node) ) 99 | 100 | def stanza_to_xml(self, stanza): 101 | d = libxml2.newDoc("1.0") 102 | r = d.newChild(None, "root", None) 103 | ns = r.newNs("jabber:server", None) 104 | r.setNs(ns) 105 | d.setRootElement(r) 106 | xml = stanza.xmlnode.docCopyNode(d, 1) 107 | r.addChild(xml) 108 | return xml,d 109 | 110 | def xml_to_xml(self, xml): 111 | d = libxml2.parseDoc(xml.serialize()) 112 | r = d.getRootElement() 113 | xml = r.children 114 | return xml, d 115 | 116 | def suite(): 117 | suite = unittest.TestSuite() 118 | suite.addTest(unittest.makeSuite(TestPresence)) 119 | return suite 120 | 121 | if __name__ == '__main__': 122 | unittest.TextTestRunner(verbosity=2).run(suite()) 123 | 124 | # vi: sts=4 et sw=4 125 | -------------------------------------------------------------------------------- /tests/stream_reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # -*- coding: UTF-8 -*- 3 | 4 | import unittest 5 | import libxml2 6 | from pyxmpp import xmlextra 7 | from pyxmpp.jid import JID,JIDError 8 | from pyxmpp import xmppstringprep 9 | 10 | def xml_elements_equal(a, b, ignore_level1_cdata = False): 11 | if a.name!=b.name: 12 | print "Name mismatch: %r, %r" % (a.name, b.name) 13 | return False 14 | try: 15 | ns1 = a.ns() 16 | except libxml2.treeError: 17 | ns1 = None 18 | try: 19 | ns2 = b.ns() 20 | except libxml2.treeError: 21 | ns2 = None 22 | if ns1 or ns2: 23 | if None in (ns1,ns2): 24 | print "Ns mismatch: %r, %r" % (ns1, ns2) 25 | return False 26 | if ns1.content != ns2.content: 27 | print "Ns mismatch: %r, %r on %r, %r" % (ns1.content, ns2.content, a, b) 28 | return False 29 | 30 | ap = a.properties 31 | bp = b.properties 32 | while 1: 33 | if (ap, bp) == (None, None): 34 | break 35 | if None in (ap, bp): 36 | return False 37 | if ap.name != bp.name: 38 | return False 39 | if ap.content != bp.content: 40 | return False 41 | ap = ap.next 42 | bp = bp.next 43 | 44 | ac = a.children 45 | bc = b.children 46 | while ac != None or bc != None: 47 | if ignore_level1_cdata: 48 | if ac and ac.type!='element': 49 | ac=ac.next 50 | continue 51 | if bc and bc.type!='element': 52 | bc=bc.next 53 | continue 54 | if None in (ac, bc): 55 | return False 56 | if ac.type != bc.type: 57 | return False 58 | if ac.type == 'element': 59 | if not xml_elements_equal(ac, bc): 60 | return False 61 | elif ac.content != bc.content: 62 | return False 63 | ac = ac.next 64 | bc = bc.next 65 | return True 66 | 67 | class EventTemplate: 68 | def __init__(self, template): 69 | self.event, offset, xml = template.split(None,2) 70 | self.offset = int(offset) 71 | self.xml = libxml2.parseDoc(eval(xml)) 72 | 73 | def __del__(self): 74 | self.xml.freeDoc() 75 | 76 | def match(self, event, node): 77 | if self.event!=event: 78 | return False 79 | if event=="end": 80 | return True 81 | if node.type!='element': 82 | return False 83 | if not xml_elements_equal(self.xml.getRootElement(),node): 84 | return False 85 | return True 86 | 87 | def __repr__(self): 88 | return "" % (self.event, self.offset, self.xml.getRootElement().serialize()) 89 | 90 | class StreamHandler(xmlextra.StreamHandler): 91 | def __init__(self, test_case): 92 | self.test_case = test_case 93 | def stream_start(self, doc): 94 | self.test_case.event("start", doc.getRootElement()) 95 | def stream_end(self, doc): 96 | self.test_case.event("end", None) 97 | def stanza(self, doc, node): 98 | self.test_case.event("node", node) 99 | 100 | expected_events = [] 101 | whole_stream = None 102 | 103 | def load_expected_events(): 104 | for l in file("data/stream_info.txt"): 105 | if l.startswith("#"): 106 | continue 107 | l=l.strip() 108 | expected_events.append(EventTemplate(l)) 109 | 110 | def load_whole_stream(): 111 | global whole_stream 112 | whole_stream = libxml2.parseFile("data/stream.xml") 113 | 114 | class TestStreamReader(unittest.TestCase): 115 | def setUp(self): 116 | self.expected_events = list(expected_events) 117 | self.handler = StreamHandler(self) 118 | self.reader = xmlextra.StreamReader(self.handler) 119 | self.file = file("data/stream.xml") 120 | self.chunk_start = 0 121 | self.chunk_end = 0 122 | self.whole_stream = libxml2.newDoc("1.0") 123 | 124 | def tearDown(self): 125 | del self.handler 126 | del self.reader 127 | self.whole_stream.freeDoc() 128 | 129 | def test_1(self): 130 | self.do_test(1) 131 | 132 | def test_2(self): 133 | self.do_test(2) 134 | 135 | def test_10(self): 136 | self.do_test(10) 137 | 138 | def test_100(self): 139 | self.do_test(100) 140 | 141 | def test_1000(self): 142 | self.do_test(1000) 143 | 144 | def do_test(self, chunk_length): 145 | while 1: 146 | data=self.file.read(chunk_length) 147 | if not data: 148 | break 149 | self.chunk_end += len(data) 150 | r=self.reader.feed(data) 151 | while r: 152 | r=self.reader.feed() 153 | if r is None: 154 | self.event("end", None) 155 | break 156 | self.chunk_start = self.chunk_end 157 | r1 = self.whole_stream.getRootElement() 158 | r2 = whole_stream.getRootElement() 159 | if not xml_elements_equal(r1, r2, True): 160 | self.fail("Whole stream invalid. Got: %r, Expected: %r" 161 | % (self.whole_stream.serialize(), whole_stream.serialize())) 162 | 163 | def event(self, event, node): 164 | expected = self.expected_events.pop(0) 165 | self.failUnless(event==expected.event, "Got %r, expected %r" % (event, expected.event)) 166 | if expected.offset < self.chunk_start: 167 | self.fail("Delayed event: %r. Expected at: %i, found at %i:%i" 168 | % (event, expected.offset, self.chunk_start, self.chunk_end)) 169 | if expected.offset > self.chunk_end: 170 | self.fail("Early event: %r. Expected at: %i, found at %i:%i" 171 | % (event, expected.offset, self.chunk_start, self.chunk_end)) 172 | if not expected.match(event,node): 173 | self.fail("Unmatched event. Expected: %r, got: %r;%r" 174 | % (expected, event, node.serialize())) 175 | if event == "start": 176 | n = node.docCopyNode(self.whole_stream,1) 177 | self.whole_stream.addChild(n) 178 | self.whole_stream.setRootElement(n) 179 | elif event == "node": 180 | n = node.docCopyNode(self.whole_stream,1) 181 | r = self.whole_stream.getRootElement() 182 | r.addChild(n) 183 | 184 | def suite(): 185 | load_expected_events() 186 | load_whole_stream() 187 | suite = unittest.TestSuite() 188 | suite.addTest(unittest.makeSuite(TestStreamReader)) 189 | return suite 190 | 191 | if __name__ == '__main__': 192 | unittest.TextTestRunner(verbosity=2).run(suite()) 193 | 194 | # vi: sts=4 et sw=4 195 | -------------------------------------------------------------------------------- /tests/vcard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import unittest 4 | 5 | import libxml2 6 | from pyxmpp.jabber import vcard 7 | 8 | def vcard2txt(vcard): 9 | """Extract data from VCard object for text comparision. 10 | Separate function defined here to test the API (attribute access).""" 11 | ret="Full name: %r\n" % (vcard.fn.value,) 12 | ret+="Structural name:\n" 13 | ret+=" Family name: %r\n" % (vcard.n.family,) 14 | ret+=" Given name: %r\n" % (vcard.n.given,) 15 | ret+=" Middle name: %r\n" % (vcard.n.middle,) 16 | ret+=" Prefix: %r\n" % (vcard.n.prefix,) 17 | ret+=" Suffix: %r\n" % (vcard.n.suffix,) 18 | for nickname in vcard.nickname: 19 | ret+="Nickname: %r\n" % (nickname.value,) 20 | for photo in vcard.photo: 21 | ret+="Photo:\n" 22 | ret+=" Type: %r\n" % (photo.type,) 23 | ret+=" Image: %r\n" % (photo.image,) 24 | ret+=" URI: %r\n" % (photo.uri,) 25 | for bday in vcard.bday: 26 | ret+="Birthday: %r\n" % (bday.value,) 27 | for adr in vcard.adr: 28 | ret+="Address:\n" 29 | ret+=" Type: %r\n" % (adr.type,) 30 | ret+=" POBox: %r\n" % (adr.pobox,) 31 | ret+=" Extended: %r\n" % (adr.extadr,) 32 | ret+=" Street: %r\n" % (adr.street,) 33 | ret+=" Locality: %r\n" % (adr.locality,) 34 | ret+=" Region: %r\n" % (adr.region,) 35 | ret+=" Postal code: %r\n" % (adr.pcode,) 36 | ret+=" Country: %r\n" % (adr.ctry,) 37 | for label in vcard.label: 38 | ret+="Label:\n" 39 | ret+=" Type: %r\n" % (label.type,) 40 | ret+=" Lines: %r\n" % (label.lines,) 41 | for tel in vcard.tel: 42 | ret+="Telephone:\n" 43 | ret+=" Type: %r\n" % (tel.type,) 44 | ret+=" Number: %r\n" % (tel.number,) 45 | for email in vcard.email: 46 | ret+="E-mail:\n" 47 | ret+=" Type: %r\n" % (email.type,) 48 | ret+=" Address: %r\n" % (email.address,) 49 | for jid in vcard.jabberid: 50 | ret+="JID: %r\n" % (jid.value,) 51 | for mailer in vcard.mailer: 52 | ret+="Mailer: %r\n" % (mailer.value,) 53 | for tz in vcard.tz: 54 | ret+="Timezone: %r\n" % (tz.value,) 55 | for geo in vcard.geo: 56 | ret+="Geographical location:\n" 57 | ret+=" Latitude: %r\n" % (geo.lat,) 58 | ret+=" Longitude: %r\n" % (geo.lon,) 59 | for title in vcard.title: 60 | ret+="Title: %r\n" % (title.value,) 61 | for role in vcard.role: 62 | ret+="Role: %r\n" % (role.value,) 63 | for logo in vcard.logo: 64 | ret+="Logo:\n" 65 | ret+=" Type: %r\n" % (logo.type,) 66 | ret+=" Image: %r\n" % (logo.image,) 67 | ret+=" URI: %r\n" % (logo.uri,) 68 | for org in vcard.org: 69 | ret+="Organization:\n" 70 | ret+=" Name: %r\n" % (org.name,) 71 | ret+=" Unit: %r\n" % (org.unit,) 72 | for cat in vcard.categories: 73 | ret+="Categories: %r\n" % (cat.keywords,) 74 | for note in vcard.note: 75 | ret+="Note: %r\n" % (note.value,) 76 | for prodid in vcard.prodid: 77 | ret+="Product id: %r\n" % (prodid.value,) 78 | for rev in vcard.rev: 79 | ret+="Revision: %r\n" % (rev.value,) 80 | for sort_string in vcard.sort_string: 81 | ret+="Sort string: %r\n" % (sort_string.value,) 82 | for sound in vcard.sound: 83 | ret+="Sound:\n" 84 | ret+=" Sound: %r\n" % (sound.sound,) 85 | ret+=" URI: %r\n" % (sound.uri,) 86 | ret+=" Phonetic: %r\n" % (sound.phonetic,) 87 | for uid in vcard.uid: 88 | ret+="User id: %r\n" % (uid.value,) 89 | for url in vcard.url: 90 | ret+="URL: %r\n" % (url.value,) 91 | try: 92 | for cls in vcard["CLASS"]: 93 | ret+="Class: %r\n" % (cls.value,) 94 | except KeyError: 95 | pass 96 | for key in vcard.key: 97 | ret+="Key:\n" 98 | ret+=" Type: %r\n" % (key.type,) 99 | ret+=" Value: %r\n" % (key.cred,) 100 | for desc in vcard.desc: 101 | ret+="Description: %r\n" % (desc.value,) 102 | return ret 103 | 104 | def xml_error_handler(ctx,error): 105 | pass 106 | 107 | class TestVCard(unittest.TestCase): 108 | def setUp(self): 109 | libxml2.registerErrorHandler(xml_error_handler,None) 110 | def tearDown(self): 111 | libxml2.registerErrorHandler(None,None) 112 | def test_xml_input1(self): 113 | xmldata=libxml2.parseFile("data/vcard1.xml") 114 | vc=vcard.VCard(xmldata.getRootElement()) 115 | should_be=file("data/vcard1.txt").read() 116 | self.failUnlessEqual(vcard2txt(vc),should_be) 117 | def test_xml_without_n(self): 118 | xmldata=libxml2.parseFile("data/vcard_without_n.xml") 119 | vc=vcard.VCard(xmldata.getRootElement()) 120 | should_be=file("data/vcard_without_n.txt").read() 121 | self.failUnlessEqual(vcard2txt(vc),should_be) 122 | def test_xml_without_fn(self): 123 | xmldata=libxml2.parseFile("data/vcard_without_n.xml") 124 | vc=vcard.VCard(xmldata.getRootElement()) 125 | should_be=file("data/vcard_without_n.txt").read() 126 | self.failUnlessEqual(vcard2txt(vc),should_be) 127 | def test_xml_with_semicolon(self): 128 | xmldata = libxml2.parseFile("data/vcard_with_semicolon.xml") 129 | vc = vcard.VCard(xmldata.getRootElement()) 130 | first = vc.rfc2426() 131 | second = vcard.VCard(first).rfc2426() 132 | self.failUnlessEqual(first, second) 133 | def test_vcf_input1(self): 134 | input=file("data/vcard2.vcf").read() 135 | vc=vcard.VCard(input) 136 | should_be=file("data/vcard2.txt").read() 137 | self.failUnlessEqual(vcard2txt(vc),should_be) 138 | def test_vcf_input2(self): 139 | input=file("data/vcard3.vcf").read() 140 | vc=vcard.VCard(input) 141 | should_be=file("data/vcard3.txt").read() 142 | self.failUnlessEqual(vcard2txt(vc),should_be) 143 | #TODO: test_xml_output 144 | 145 | def suite(): 146 | suite = unittest.TestSuite() 147 | suite.addTest(unittest.makeSuite(TestVCard)) 148 | return suite 149 | 150 | if __name__ == '__main__': 151 | unittest.TextTestRunner(verbosity=2).run(suite()) 152 | 153 | # vi: sts=4 et sw=4 154 | -------------------------------------------------------------------------------- /utils/migrate-0_5-0_6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import re 5 | import os 6 | 7 | args=sys.argv[1:] 8 | if not args or "-h" in args or "--help" in args: 9 | print "PyXMPP 0.5 to 0.6 code updater." 10 | print "Usage:" 11 | print " %s file..." % (sys.argv[0],) 12 | print 13 | print "This script will try to update your code for the recent changes" 14 | print "in the PyXMPP package. But this updates are just simple regexp" 15 | print "substitutions which may _break_ your code. Always check the result." 16 | sys.exit(0) 17 | 18 | 19 | in_par=r"(?:\([^)]*\)|[^()])" 20 | 21 | 22 | updates=[ 23 | (r"(\b(?:Muc)?(?:Stanza|Message|Iq|Presence)\("+in_par+r"*)\bfr=("+in_par+r"+\))",r"\1from_jid=\2"), 24 | (r"(\b(?:Muc)?(?:Stanza|Message|Iq|Presence)\("+in_par+r"*)\bto=("+in_par+r"+\))",r"\1to_jid=\2"), 25 | (r"(\b(?:Muc)?(?:Stanza|Message|Iq|Presence)\("+in_par+r"*)\btype?=("+in_par+r"+\))",r"\1stanza_type=\2"), 26 | (r"(\b(?:Muc)?(?:Stanza|Message|Iq|Presence)\("+in_par+r"*)\bs?id=("+in_par+r"+\))",r"\1stanza_id=\2"), 27 | ] 28 | 29 | updates=[(re.compile(u_re,re.MULTILINE|re.DOTALL),u_repl) 30 | for u_re,u_repl in updates] 31 | 32 | for fn in args: 33 | print fn+":", 34 | orig_code=file(fn).read() 35 | changes_made=0 36 | code=orig_code 37 | for u_re,u_repl in updates: 38 | (code,cm)=u_re.subn(u_repl,code) 39 | changes_made+=cm 40 | if changes_made: 41 | print changes_made,"changes" 42 | os.rename(fn,fn+".bak") 43 | file(fn,"w").write(code) 44 | else: 45 | print "no changes" 46 | 47 | 48 | # vi: sts=4 et sw=4 49 | --------------------------------------------------------------------------------