This tutorial was started because there was a need for a small xmpppy-tutorial fitting the gap between the small examples and the bare api specs. The small examples distributed with it are fine, but when you're building you're own bot and fiddling around with things like rostermanagement, you wish that there's something between the bare API-Specs and the small examples. So i decided to write down my experiences and publish them..
This paper should help you to get started and covers the basic issues of programming with xmpppy.
If you want to programm a bot, you will find some hints in the last chapters.
I assume that you have already python knowledge, python basics are not covered by this tutorial.
Furthermore no Jabber specific terms (such as "jid") will be explained here. Please use google to clarify this things.
This tutorial was written for a linux audience. I don't know if xmpppy works on Windows or *nix, so you've got to solve os-dependend problems by yourself.
If you like to contribute to this document or found a bug feel free to contact me.
Jabber/xmpp is an Open Source instant messaging protocol. It is described in the RFCs
3920 - 3923 and is based on XML. A lot of Open Source and even commercial servers, clients and libraries are available.
It has several advanced features such as encryption and conferences.
Jabber beats ICQ and other protocols because the closed protocols are changing often and the libraries have to deal
with that fact. Informations about these protocols are often the result of reverse engineering. This work has to be
done every time the protocol changes. As you might know, this is a great problem for small software projects..
I used xmpppy because it was available as a debian package and it's small. The design of xmpppy was exactly the thing i was looking for. There are high level functions for sending messages, but you can also build your messages as xml-strings if you wish to or if you need special features.
xmpppy is the inofficial successor of jabber.py, which i used before. The jabber.py project is dead now, so i migrated to xmpppy, which inherited some code from jabber.py and has a similar API.
If you're interested in other libraries, have a look at http://www.jabber.org.
Before you start with this tutorial you should gather informations about xmpppy and jabber.
You will need the API-Overview which is available at the project homepage. There are also some example programs available. The examples in this book are mainly based on these online examples.
Spend some time on the jabber.org or ask the wikipedia. If you're not new to this things, skip this..
For the best available documentation on jabber in general you should take a look at the RFCs. They're easier to read than you might expect and contain a lot of useful informations.
If you look for a concrete implementation of something, have a look at the projects using xmpppy under "Resources".
If you want to get in contact with the xmpppy developers or just want to keep track of the current development, join the xmpppy-devel mailing list at https://lists.sourceforge.net/lists/listinfo/xmpppy-devel.
This work is licensed under the creative commons license Attribution-NonCommercial 2.5.
You can obtain it at http://creativecommons.org/licenses/by-nc/2.5/
Use the package-management tool of your distribution to determine if a xmpppy package exists.
If there are no adequate packages or if you want the newest version, you have to build the software from the sources.
Get the newest tarball from the download page at the project's homepage (http://xmpppy.sourceforge.net) and extract it (The filenames and URLs are examples)
wget http://optusnet.dl.sourceforge.net/sourceforge/xmpppy/xmpppy-0.3.1.tar.gz
tar xzf xmpppy-0.3.1.tar.gz
cd xmpppy-0.3.1
python setup.py install
If you need more informations, have a look at the README file distributed with xmpppy.
Before we start with coding, you should check some preconditions. An important thing is the right jabber client. I would therefore recommend the use of psi. Psi is a feature-rich jabber-only client. It supports service browsing and it implements the most features as described in the RFC. Especially the group chat support is better implemented than in the most other clients.
Of course you can use a mutliprotocol client like Gaim or kopete too, but they implement some things on a different way.. For example gaim makes no difference between the multiple message types (chat, message..).
If you want to develop something with groupchat, you may want to setup your own jabber server.
This may be recommended if you're new to xmpppy and you don't know exactly what you're doing.
Think about the possibility of disturbing other people or - in the worst case - killing a server.
If your bot gets out of control and sends every 10ms a message to the server, the server admin might be a little bit disgusted of your public beta testing. So keep in mind that there are other people (and of course bots) out there while your bot uses a public server.
Furthermore i assume that you have already registered a jabber account.
If you're completely new to jabber, you should read something about the underlying techniques such as XML-Streams. A good starting point is the xmpp-core RFC.
And now for something completely different.
Every tutorial on programming starts with a tiny "Hello World" program, so why break with this tradition.
Our program connects to a jabber server, authenticates itself with a username/password combination and sends a "Hello
World!" message to a specified jid. It doesn't make much sense, but its a good start. Maybe you can use it in shell scripts or something..
The first thing to do in every jabber session is to connect to your jabber server.
Line 11 shows you how to do this. xmpp.Client() returns a Client instance, which is our basic object.
You can think of the client-object as your connection to the server. If your server uses an unusual port, you can pass it to the xmpp.Client constructor. Its signature is: xmpp.Client(server,port,debug).
The most work happens in Line 13 and 15. We're connecting to the server and trying to authenticate. That means the server just checks if our password is correct. jid.getNode() returns everything before the "@" in your jid.
Line 17 finally does the magic: It sends a message (with content msg) to recipient.
After that, we should disconnect from the server to make a gracefull exit.
You may have noticed that message you received from that script was shown to you on a different way than normal chat messages. If you use a pure jabber client like psi, which is very near to the jabber standart, the message might be shown like an email or something. That is because the xmpp protocol defines multiple message types. Less xmpp-compliant messengers like kopete make no difference between them.
RFC 3921 2.1.1 defines 5 message types: chat,error,groupchat,headline and normal. For detailed informations see http://www.apps.ietf.org/rfc/rfc3921.html.
At this time we want to focus on 'chat' and 'normal'. In our example above, we did not define any message type, so
psi interprets this as "normal". "normal" is described as a single message, without history. I suppose
you want to change that behaviour to 'chat' messages, so we have to set the type explicitly.
Now switch to the API and look after the methods of xmpp.Protocol. You'll discover a method called setType.
That sounds suitable, eh? Change line 17 to:
Receiving messages is a little bit more complicated than sending messages. You need an event loop and an handler to do this.
Message handlers are a basic concept for acting on events. This means that you have to tell the xmpp.Client Object which method it should call if a message arrives. But first of all explanations, have a look at the code:
We should focus on the main() method to understand how everything works. The most code should appear familiar to you. Line 37 holds one new method:
RegisterHandler('message', messageCB)
As you may have already guessed, the method "messageCB" is registered as a callback handler for incoming messages. So if you want to react an incoming messages, write a method called "messageCB" (as in line 8)
and place your message-handling code here. Be sure that your method takes 2 parameters.
The first parameter is the connection.The second parameter an x.protocol.Message instance. It is printed on your terminal if a message arrives. Search the API-Docs for it and experiment with the given methods gather experience. Maybe you could combine it with the example from Chapter 1 to react on certain messages.
If this example doesn't work for you, uncomment line 38. cl.sendInitPresence() tells our server that we're online. Some servers (for example Google's gtalk service) won't send messages to you if you're not marked as 'online'. A detailed look on presence handling is given in the next chapter.
The resource of a jid is a nice jabber feature. With the use of different resources, a jid can be logged into a server server several times. This means you can be online with more then one client at the same time.
Setting the jid is a very simple thing: A third argument has to be passed to the auth() method.
So the authentication step may look like this:
Presence events are those messages which contain informations about your status and subscription.
You may have wondered why our bot from example 2 wasn't shown as "online" in your contact list.
This was because we didn't notify the server that the bot is online. This could be done by "sendInitPresence()". Go back to example 2 and uncomment the appropriate line. If the bot is in your roster, he will be marked as "online".
The next thing about presence covers subscription. "subscription" in generally means: "Allow somebody to see if you're online and allow him to add you to his roster".
To handle this events, we have to register a presence handler. This is done on the same way as the message handler in example 2.
This is the most simple presence handler. When you receive a message containing "subscribe", return a "subscribed" answer and ask him for subscription. That means subscribing everyone who asks.
You can imagine that this is not the right thing for real world applications. I prefer limits like a maximal roster size and a policy to add users to the roster. This may differ for your bot..
Think about who should be able to use your bot and block everyone else.
Many applications need the status of an user. They want to know if he's online or offline or maybe only away.
This is not as easy as it seems. There's no direct way to get the Status of a given jid. You have to analyse the presence messages the oponnents send. If a user gets online or if you go online, everyone who has subscribed to you will send a presence message. Of course this works only if the user is online.
If a user logs out, he sends you a presence message with the Status "Logged out". Everytime he changes his status, you will receive a corresponding status message.
My workaround for the problem: keep track of the actual status with a dictionary. Take the jid as the key
and set their value if you receive a presence message for the jid.
If you know a better way to do this, please contact me !
The roster is the jabber synonym for the better known term "contact list". You can easily fetch, add or remove entries.
In the most cases you won't have to deal with this, because the most actions are done automatically.
For example if you subscribe to someones presence, he will be added to your roster.
A common example for a manual use of roster functions is to retrieve the jids contained in your roster.
Therefore xmpppy offers you a higher level class to operate on rosters: x.roster.Roster
The class should be self-explanatory if you have a look at
http://xmpppy.sourceforge.net/apidocs/public/x.roster.Roster-class.html.
A lot of applications are acting on disconnects. This could happen if your jabber server crashes or if you have lost your internet connection. The action may differ, but a possible action would be displaying a message and reconnect..
To act on disconnects, add a disconnect-handler. This is done analogical to the known event handlers.
Write a function DisconnectHandler(self) and register it via RegisterDisconnectHandler(self, DisconnectHandler).
I suppose most of you have experiences with many-to-many chats like IRC. Jabber has its own many-to-many chat which is implement by two protocols: groupchat 1.0 and multi user chat (MUC).
The MUC protocol is based on groupchat and allows a lot of advanced features like user privileges and passwort-protected rooms.
Because of the simple implementation groupchat will focused here.
If you need MUC, have a look at http://www.jabber.org/jeps/jep-0045.html.
Using groupchat 1.0 is very easy because it is based on presence messages.
First we have to think about what we want to do. Let's imagine that we want to join the room "castle_anthrax" with the nickname "zoot". The server is called "conference.holy-gra.il".
To enter a room, the only thing to do is sending a presence message with the room and your nickname to your conference server.
To speak in python:
Of course it could happen that someone in this room has already chosen the nickname "zoot". This will cause an error 409 ("Conflict") and we have to try another nickname.
If nobody named "zoot" is in there, we'll receive a presence message from everybody in that room (including yourself).
The "from" attribut of the message looks like this: castle_anthrax@conference.holy-gra.il/galahad, which means that somebody named galahad is already in this room.This might be interesting for you if you write a jabber client and you have to keep track of the nicks in the room.
Receiving messages is divided in 2 parts. If someone sends a public message, you'll receive a message with the type "groupchat".This message will be sent to every user in that room.A private message is send as a chat type message.
Sending message is as easy as receiving messages. Just send a groupchat message to the room-jid or send a chat message to a room-member, if you like some private chit-chat..
The following example is based on recv.py, the script used in chapter 2: receiving messages.
If you don't know Knigge, see http://en.wikipedia.org/wiki/Knigge.
His main work is a book called "Ueber den Umgang mit Menschen" ("On Human Relations").
As it appears clear that there are (social) rules for human relation, not everybody knows that there a rules
for bot communication too.
As i already noted in chapter 3, developing bots is more than a technical thing.
You should consider that you are responsible for your bot. Imagine you got something wrong and your bot is crashing his server by sending messages every millisecond. That's not a great deal if you use a dedicated server for testing, but that's unusual.
The following hints are loosely based on the rules given by
http://web.swissjabber.chindex.php/Regeln_zum_Betrieb_von_Bots_und_Robots.
As a result of abuse by out-of-control bots, some server admins ban every bot which is not written with the following rules in mind:
use a "help" command which identifies the function and the administrator of your bot
ignore messages from jids not including a "@"
perfom regular logins only in an interval of five minutes
reply only to jids with the status: available, chat, away, xa or dnd
xmpppy-0.4.1/doc/xmpppy-guide/README 0000644 0001750 0000050 00000001260 10512362040 015751 0 ustar norman src This document was imported with from
http://developer.berlios.de/projects/xmpppy-guide/
Very minor changes were applied after converting it with latex2html
Author is Sebastian Moors
License is unknown but Sebastian Moors explicitly allowed me
to include this document into 0.4 release:
========================================
From: Sebastian Moors sebastian dot moors at googlemail dot com
To: xmpppy-devel at lists dot sourceforge dot net
Date: Wed, 4 Oct 2006 09:39:12 +0200
> BTW - can we include this document into upcoming 0.4 release of xmpppy? I.e.
> put it into tarball?
Of course, that would be great.
========================================
--
Alexey Nezhdanov
2006.10.09
xmpppy-0.4.1/doc/advanced.html 0000644 0001750 0000050 00000011755 10163471171 015116 0 ustar norman src
Xmpppy usage - advanced.
Introduction
To write a programs using XMPP technology you must understand the basic
principles of it. Xmpppy uses it's own implementation of XML handling
procedures - so you should get used to it. Though it is simple enough I hope.
Note that 'name' argument really consists of namespace and node name, space separated. Example:
node=Node('jabber:client message', attrs={'to':'target@jid.com'},payload=[Node('body',payload=['Hello target!'])])
or
node=Node('jabber:client message')
node['to']='target@jid.com'
node.NT.body='Hello target!'
NT stands for 'New Tag' and explicitly adds new child to the current node.
Also the T can be used. That means "find Tag" but if tag exists it acts just like NT otherwise.
Protocol class
Uses similar syntax. We will use 'node' attribute now:
p=Protocol(node=node)
or
proto=Protocol('message',to='target@jid.com',payload=[Node('body',payload=['Hello target!'])])
or
proto=Protocol('message',to='target@jid.com')
proto.NT.body='Hello target!'
iq=Iq('set',NS_AUTH,payload=[Node('username',payload=['user']),Node('password',payload=['secret'])])
or
iq=Iq('set',NS_AUTH)
iq.T.query.NT.username='user'
iq.T.query.NT.password='secret'
or
iq=Iq('set',NS_AUTH)
iq.T.query.T.username='user'
iq.T.query.T.password='secret'
As I already noted - 'T' acts just like 'NT' if tag doesn't exists.
pres=Presence(priority=5, show='xa',status="I'm away from my computer")
or
pres=Presence()
pres.setPriority(5)
pres.setShow('xa')
pres.setStatus("I'm away from my computer")
pres.setTimestamp()
or
pres=Presence()
pres.T.priority=5
pres.T.show='xa'
pres.T.status="I'm away from my computer"
pres.setTimestamp()
English is not my native language. If you see any bugs in this text, please, let me know.
Basic
Introduction.
This documents topic is for people who want to quickly try xmpppy for a simple task, like
writing a command-line script for sending a single message.
Writing a simple script
This example demonstrates a simple script that sends message to one
recipient.
Example:
xsend test@jabber.org Hello there!
You don't have a similar tool in your toolkit? Using the xmpppy
library it can be created easily.
What? Already have one? Hmm. Maybe you want to simplify things a bit, or just curious how
to do it one more way? Anyway - let's start now!
First - we declare ourself as a python script and importing needed modules:
#!/usr/bin/python
import sys,os,xmpp
After it we have to check if we have enough arguments on the command-line:
if len(sys.argv) < 2:
print "Syntax: xsend JID text"
sys.exit(0)
After it we must decode arguments. Omitting all checks to simplify our script:
tojid=sys.argv[1]
text=' '.join(sys.argv[2:])
One more non-jabber step: We have to to get our Jabber ID and login
details. Presuming that all info
stored in ~/.xsend file:
jidparams={}
if os.access(os.environ['HOME']+'/.xsend',os.R_OK):
for ln in open(os.environ['HOME']+'/.xsend').readlines():
key,val=ln.strip().split('=',1)
jidparams[key.lower()]=val
for mandatory in ['jid','password']:
if mandatory not in jidparams.keys():
open(os.environ['HOME']+'/.xsend','w').write('#JID=romeo@montague.net\n#PASSWORD=juliet\n')
print 'Please ensure the ~/.xsend file has valid JID for sending messages.'
sys.exit(0)
Phew! The most complex (non-jabber ;) ) part is finished. From now on we have to:
connect to jabber server
authenticate ourselves
submit a message
Let's start:
0. To connect we must have a client instance. Calculating our server name and
creating Client class instance for it:
2. We can go online now (by sending the inital presence) but will not do that, as it is not nessessary for sending a message.
So we send a message now!
We're done! The session must now be closed but since we have not registered
disconnect handler we will just leave it to python and TCP/IP layer.
All jabber servers that I know handle such
disconnects correctly.
You can download this script here.
What now?
If you were impressed of how the things were done with xmpppy, you may be interested in
more thorough examination of xmpppy library. The "advanced" and "expert"
parts of this document are here to help you.
"Advanced" (isn't writed yet) part is much like another tutorial and
describing common principles of XMPP usage via xmpppy prism. It describes ideas that are the foundation of XML handling with the
simplexml library, the essence of dispatcher's work and how messages are processed, and
some guidelines to write more complex programs and uses of the library.
"Expert" part is full library API description documentation.
This is epydoc generated output - all info is taken from the xmpppy code so you can re-generate it at any time.
xmpppy-0.4.1/doc/apidocs/ 0002755 0001750 0000050 00000000000 10731034076 014076 5 ustar norman src xmpppy-0.4.1/doc/apidocs/epydoc.css 0000644 0001750 0000050 00000036430 10731034035 016072 0 ustar norman src
/* Epydoc CSS Stylesheet
*
* This stylesheet can be used to customize the appearance of epydoc's
* HTML output.
*
*/
/* Default Colors & Styles
* - Set the default foreground & background color with 'body'; and
* link colors with 'a:link' and 'a:visited'.
* - Use bold for decision list terms.
* - The heading styles defined here are used for headings *within*
* docstring descriptions. All headings used by epydoc itself use
* either class='epydoc' or class='toc' (CSS styles for both
* defined below).
*/
body { background: #ffffff; color: #000000; }
a:link { color: #0000ff; }
a:visited { color: #204080; }
dt { font-weight: bold; }
h1 { font-size: +140%; font-style: italic;
font-weight: bold; }
h2 { font-size: +125%; font-style: italic;
font-weight: bold; }
h3 { font-size: +110%; font-style: italic;
font-weight: normal; }
code { font-size: 100%; }
/* Page Header & Footer
* - The standard page header consists of a navigation bar (with
* pointers to standard pages such as 'home' and 'trees'); a
* breadcrumbs list, which can be used to navigate to containing
* classes or modules; options links, to show/hide private
* variables and to show/hide frames; and a page title (using
*
). The page title may be followed by a link to the
* corresponding source code (using 'span.codelink').
* - The footer consists of a navigation bar, a timestamp, and a
* pointer to epydoc's homepage.
*/
h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; }
h2.epydoc { font-size: +130%; font-weight: bold; }
h3.epydoc { font-size: +115%; font-weight: bold; }
td h3.epydoc { font-size: +115%; font-weight: bold;
margin-bottom: 0; }
table.navbar { background: #a0c0ff; color: #000000;
border: 2px groove #c0d0d0; }
table.navbar table { color: #000000; }
th.navbar-select { background: #70b0ff;
color: #000000; }
table.navbar a { text-decoration: none; }
table.navbar a:link { color: #0000ff; }
table.navbar a:visited { color: #204080; }
span.breadcrumbs { font-size: 85%; font-weight: bold; }
span.options { font-size: 70%; }
span.codelink { font-size: 85%; }
td.footer { font-size: 85%; }
/* Table Headers
* - Each summary table and details section begins with a 'header'
* row. This row contains a section title (marked by
* 'span.table-header') as well as a show/hide private link
* (marked by 'span.options', defined above).
* - Summary tables that contain user-defined groups mark those
* groups using 'group header' rows.
*/
td.table-header { background: #70b0ff; color: #000000;
border: 1px solid #608090; }
td.table-header table { color: #000000; }
td.table-header table a:link { color: #0000ff; }
td.table-header table a:visited { color: #204080; }
span.table-header { font-size: 120%; font-weight: bold; }
th.group-header { background: #c0e0f8; color: #000000;
text-align: left; font-style: italic;
font-size: 115%;
border: 1px solid #608090; }
/* Summary Tables (functions, variables, etc)
* - Each object is described by a single row of the table with
* two cells. The left cell gives the object's type, and is
* marked with 'code.summary-type'. The right cell gives the
* object's name and a summary description.
* - CSS styles for the table's header and group headers are
* defined above, under 'Table Headers'
*/
table.summary { border-collapse: collapse;
background: #e8f0f8; color: #000000;
border: 1px solid #608090;
margin-bottom: 0.5em; }
td.summary { border: 1px solid #608090; }
code.summary-type { font-size: 85%; }
table.summary a:link { color: #0000ff; }
table.summary a:visited { color: #204080; }
/* Details Tables (functions, variables, etc)
* - Each object is described in its own div.
* - A single-row summary table w/ table-header is used as
* a header for each details section (CSS style for table-header
* is defined above, under 'Table Headers').
*/
table.details { border-collapse: collapse;
background: #e8f0f8; color: #000000;
border: 1px solid #608090;
margin: .2em 0 0 0; }
table.details table { color: #000000; }
table.details a:link { color: #0000ff; }
table.details a:visited { color: #204080; }
/* Fields */
dl.fields { margin-left: 2em; margin-top: 1em;
margin-bottom: 1em; }
dl.fields dd ul { margin-left: 0em; padding-left: 0em; }
div.fields { margin-left: 2em; }
div.fields p { margin-bottom: 0.5em; }
/* Index tables (identifier index, term index, etc)
* - link-index is used for indices containing lists of links
* (namely, the identifier index & term index).
* - index-where is used in link indices for the text indicating
* the container/source for each link.
* - metadata-index is used for indices containing metadata
* extracted from fields (namely, the bug index & todo index).
*/
table.link-index { border-collapse: collapse;
background: #e8f0f8; color: #000000;
border: 1px solid #608090; }
td.link-index { border-width: 0px; }
table.link-index a:link { color: #0000ff; }
table.link-index a:visited { color: #204080; }
span.index-where { font-size: 70%; }
table.metadata-index { border-collapse: collapse;
background: #e8f0f8; color: #000000;
border: 1px solid #608090;
margin: .2em 0 0 0; }
td.metadata-index { border-width: 1px; border-style: solid; }
table.metadata-index a:link { color: #0000ff; }
table.metadata-index a:visited { color: #204080; }
/* Function signatures
* - sig* is used for the signature in the details section.
* - .summary-sig* is used for the signature in the summary
* table, and when listing property accessor functions.
* */
.sig-name { color: #006080; }
.sig-arg { color: #008060; }
.sig-default { color: #602000; }
.summary-sig { font-family: monospace; }
.summary-sig-name { color: #006080; font-weight: bold; }
table.summary a.summary-sig-name:link
{ color: #006080; font-weight: bold; }
table.summary a.summary-sig-name:visited
{ color: #006080; font-weight: bold; }
.summary-sig-arg { color: #006040; }
.summary-sig-default { color: #501800; }
/* To render variables, classes etc. like functions */
table.summary .summary-name { color: #006080; font-weight: bold;
font-family: monospace; }
table.summary
a.summary-name:link { color: #006080; font-weight: bold;
font-family: monospace; }
table.summary
a.summary-name:visited { color: #006080; font-weight: bold;
font-family: monospace; }
/* Variable values
* - In the 'variable details' sections, each varaible's value is
* listed in a 'pre.variable' box. The width of this box is
* restricted to 80 chars; if the value's repr is longer than
* this it will be wrapped, using a backslash marked with
* class 'variable-linewrap'. If the value's repr is longer
* than 3 lines, the rest will be ellided; and an ellipsis
* marker ('...' marked with 'variable-ellipsis') will be used.
* - If the value is a string, its quote marks will be marked
* with 'variable-quote'.
* - If the variable is a regexp, it is syntax-highlighted using
* the re* CSS classes.
*/
pre.variable { padding: .5em; margin: 0;
background: #dce4ec; color: #000000;
border: 1px solid #708890; }
.variable-linewrap { color: #604000; font-weight: bold; }
.variable-ellipsis { color: #604000; font-weight: bold; }
.variable-quote { color: #604000; font-weight: bold; }
.variable-group { color: #008000; font-weight: bold; }
.variable-op { color: #604000; font-weight: bold; }
.variable-string { color: #006030; }
.variable-unknown { color: #a00000; font-weight: bold; }
.re { color: #000000; }
.re-char { color: #006030; }
.re-op { color: #600000; }
.re-group { color: #003060; }
.re-ref { color: #404040; }
/* Base tree
* - Used by class pages to display the base class hierarchy.
*/
pre.base-tree { font-size: 80%; margin: 0; }
/* Frames-based table of contents headers
* - Consists of two frames: one for selecting modules; and
* the other listing the contents of the selected module.
* - h1.toc is used for each frame's heading
* - h2.toc is used for subheadings within each frame.
*/
h1.toc { text-align: center; font-size: 105%;
margin: 0; font-weight: bold;
padding: 0; }
h2.toc { font-size: 100%; font-weight: bold;
margin: 0.5em 0 0 -0.3em; }
/* Syntax Highlighting for Source Code
* - doctest examples are displayed in a 'pre.py-doctest' block.
* If the example is in a details table entry, then it will use
* the colors specified by the 'table pre.py-doctest' line.
* - Source code listings are displayed in a 'pre.py-src' block.
* Each line is marked with 'span.py-line' (used to draw a line
* down the left margin, separating the code from the line
* numbers). Line numbers are displayed with 'span.py-lineno'.
* The expand/collapse block toggle button is displayed with
* 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not
* modify the font size of the text.)
* - If a source code page is opened with an anchor, then the
* corresponding code block will be highlighted. The code
* block's header is highlighted with 'py-highlight-hdr'; and
* the code block's body is highlighted with 'py-highlight'.
* - The remaining py-* classes are used to perform syntax
* highlighting (py-string for string literals, py-name for names,
* etc.)
*/
pre.py-doctest { padding: .5em; margin: 1em;
background: #e8f0f8; color: #000000;
border: 1px solid #708890; }
table pre.py-doctest { background: #dce4ec;
color: #000000; }
pre.py-src { border: 2px solid #000000;
background: #f0f0f0; color: #000000; }
.py-line { border-left: 2px solid #000000;
margin-left: .2em; padding-left: .4em; }
.py-lineno { font-style: italic; font-size: 90%;
padding-left: .5em; }
a.py-toggle { text-decoration: none; }
div.py-highlight-hdr { border-top: 2px solid #000000;
border-bottom: 2px solid #000000;
background: #d8e8e8; }
div.py-highlight { border-bottom: 2px solid #000000;
background: #d0e0e0; }
.py-prompt { color: #005050; font-weight: bold;}
.py-more { color: #005050; font-weight: bold;}
.py-string { color: #006030; }
.py-comment { color: #003060; }
.py-keyword { color: #600000; }
.py-output { color: #404040; }
.py-name { color: #000050; }
.py-name:link { color: #000050 !important; }
.py-name:visited { color: #000050 !important; }
.py-number { color: #005000; }
.py-defname { color: #000060; font-weight: bold; }
.py-def-name { color: #000060; font-weight: bold; }
.py-base-class { color: #000060; }
.py-param { color: #000060; }
.py-docstring { color: #006030; }
.py-decorator { color: #804020; }
/* Use this if you don't want links to names underlined: */
/*a.py-name { text-decoration: none; }*/
/* Graphs & Diagrams
* - These CSS styles are used for graphs & diagrams generated using
* Graphviz dot. 'img.graph-without-title' is used for bare
* diagrams (to remove the border created by making the image
* clickable).
*/
img.graph-without-title { border: none; }
img.graph-with-title { border: 1px solid #000000; }
span.graph-title { font-weight: bold; }
span.graph-caption { }
/* General-purpose classes
* - 'p.indent-wrapped-lines' defines a paragraph whose first line
* is not indented, but whose subsequent lines are.
* - The 'nomargin-top' class is used to remove the top margin (e.g.
* from lists). The 'nomargin' class is used to remove both the
* top and bottom margin (but not the left or right margin --
* for lists, that would cause the bullets to disappear.)
*/
p.indent-wrapped-lines { padding: 0 0 0 7em; text-indent: -7em;
margin: 0; }
.nomargin-top { margin-top: 0; }
.nomargin { margin-top: 0; margin-bottom: 0; }
/* HTML Log */
div.log-block { padding: 0; margin: .5em 0 .5em 0;
background: #e8f0f8; color: #000000;
border: 1px solid #000000; }
div.log-error { padding: .1em .3em .1em .3em; margin: 4px;
background: #ffb0b0; color: #000000;
border: 1px solid #000000; }
div.log-warning { padding: .1em .3em .1em .3em; margin: 4px;
background: #ffffb0; color: #000000;
border: 1px solid #000000; }
div.log-info { padding: .1em .3em .1em .3em; margin: 4px;
background: #b0ffb0; color: #000000;
border: 1px solid #000000; }
h2.log-hdr { background: #70b0ff; color: #000000;
margin: 0; padding: 0em 0.5em 0em 0.5em;
border-bottom: 1px solid #000000; font-size: 110%; }
p.log { font-weight: bold; margin: .5em 0 .5em 0; }
tr.opt-changed { color: #000000; font-weight: bold; }
tr.opt-default { color: #606060; }
pre.log { margin: 0; padding: 0; padding-left: 1em; }
xmpppy-0.4.1/doc/apidocs/epydoc.js 0000644 0001750 0000050 00000023602 10731034035 015713 0 ustar norman src function toggle_private() {
// Search for any private/public links on this page. Store
// their old text in "cmd," so we will know what action to
// take; and change their text to the opposite action.
var cmd = "?";
var elts = document.getElementsByTagName("a");
for(var i=0; i";
s += " ";
for (var i=0; i... ";
elt.innerHTML = s;
}
}
function toggle(id) {
elt = document.getElementById(id+"-toggle");
if (elt.innerHTML == "-")
collapse(id);
else
expand(id);
return false;
}
function highlight(id) {
var elt = document.getElementById(id+"-def");
if (elt) elt.className = "py-highlight-hdr";
var elt = document.getElementById(id+"-expanded");
if (elt) elt.className = "py-highlight";
var elt = document.getElementById(id+"-collapsed");
if (elt) elt.className = "py-highlight";
}
function num_lines(s) {
var n = 1;
var pos = s.indexOf("\n");
while ( pos > 0) {
n += 1;
pos = s.indexOf("\n", pos+1);
}
return n;
}
// Collapse all blocks that mave more than `min_lines` lines.
function collapse_all(min_lines) {
var elts = document.getElementsByTagName("div");
for (var i=0; i 0)
if (elt.id.substring(split, elt.id.length) == "-expanded")
if (num_lines(elt.innerHTML) > min_lines)
collapse(elt.id.substring(0, split));
}
}
function expandto(href) {
var start = href.indexOf("#")+1;
if (start != 0 && start != href.length) {
if (href.substring(start, href.length) != "-") {
collapse_all(4);
pos = href.indexOf(".", start);
while (pos != -1) {
var id = href.substring(start, pos);
expand(id);
pos = href.indexOf(".", pos+1);
}
var id = href.substring(start, href.length);
expand(id);
highlight(id);
}
}
}
function kill_doclink(id) {
var parent = document.getElementById(id);
parent.removeChild(parent.childNodes.item(0));
}
function auto_kill_doclink(ev) {
if (!ev) var ev = window.event;
if (!this.contains(ev.toElement)) {
var parent = document.getElementById(this.parentID);
parent.removeChild(parent.childNodes.item(0));
}
}
function doclink(id, name, targets_id) {
var elt = document.getElementById(id);
// If we already opened the box, then destroy it.
// (This case should never occur, but leave it in just in case.)
if (elt.childNodes.length > 1) {
elt.removeChild(elt.childNodes.item(0));
}
else {
// The outer box: relative + inline positioning.
var box1 = document.createElement("div");
box1.style.position = "relative";
box1.style.display = "inline";
box1.style.top = 0;
box1.style.left = 0;
// A shadow for fun
var shadow = document.createElement("div");
shadow.style.position = "absolute";
shadow.style.left = "-1.3em";
shadow.style.top = "-1.3em";
shadow.style.background = "#404040";
// The inner box: absolute positioning.
var box2 = document.createElement("div");
box2.style.position = "relative";
box2.style.border = "1px solid #a0a0a0";
box2.style.left = "-.2em";
box2.style.top = "-.2em";
box2.style.background = "white";
box2.style.padding = ".3em .4em .3em .4em";
box2.style.fontStyle = "normal";
box2.onmouseout=auto_kill_doclink;
box2.parentID = id;
// Get the targets
var targets_elt = document.getElementById(targets_id);
var targets = targets_elt.getAttribute("targets");
var links = "";
target_list = targets.split(",");
for (var i=0; i" +
target[0] + "";
}
// Put it all together.
elt.insertBefore(box1, elt.childNodes.item(0));
//box1.appendChild(box2);
box1.appendChild(shadow);
shadow.appendChild(box2);
box2.innerHTML =
"Which "+name+" do you want to see documentation for?" +
"
";
}
return false;
}
function get_anchor() {
var href = location.href;
var start = href.indexOf("#")+1;
if ((start != 0) && (start != href.length))
return href.substring(start, href.length);
}
function redirect_url(dottedName) {
// Scan through each element of the "pages" list, and check
// if "name" matches with any of them.
for (var i=0; i-m" or "-c";
// extract the portion & compare it to dottedName.
var pagename = pages[i].substring(0, pages[i].length-2);
if (pagename == dottedName.substring(0,pagename.length)) {
// We've found a page that matches `dottedName`;
// construct its URL, using leftover `dottedName`
// content to form an anchor.
var pagetype = pages[i].charAt(pages[i].length-1);
var url = pagename + ((pagetype=="m")?"-module.html":
"-class.html");
if (dottedName.length > pagename.length)
url += "#" + dottedName.substring(pagename.length+1,
dottedName.length);
return url;
}
}
}
xmpppy-0.4.1/doc/apidocs/crarr.png 0000644 0001750 0000050 00000000524 10731034035 015707 0 ustar norman src ┴PNG
IHDR
e╒E░ ,tEXtCreation Time Tue 22 Aug 2006 00:43:10 -0500`X tIMEж)с}ж pHYs б бnпu> gAMA ╠▐Эa EPLTEЪЪЪмц╟вою─f4sW Ашп┼rD`@ bCэухИДэ√│X{`,╞÷─lN┤o@УСП╙≥xdEПМХ·┼dпф╢■~Tжwеv tRNS @Фьf MIDATxзc`@╪Л╪0&+ ≈┼┬╟╩(▓┬─═;;/ПEXЫь▒?пn ┐╙├≈ b;'╙+≤≤Yп#° (r<ё" IEND╝B`┌ xmpppy-0.4.1/doc/apidocs/identifier-index.html 0000644 0001750 0000050 00001043661 10731034040 020213 0 ustar norman src
Identifier Index
xmpp.client.PlugIn:
Common xmpppy plugins infrastructure: plugging in/out,
debugging.
xmpp.auth.Bind:
Bind some JID to the current connection to allow router know of
our location.
xmpp.auth.NonSASL:
Implements old Non-SASL (JEP-0078) authentication used in
jabberd1.4 and transport authentication.
xmpp.commands.Command_Handler_Prototype:
This is a prototype command handler, as each command uses a disco method
and execute method you can implement it any way you like, however this is
my first attempt at making a generic handler that you can hang process
stages on too.
xmpp.session.Session:
The Session class instance is used for storing all
session-related info like credentials, socket/xml stream/session
state flags, roster items (in case of client type connection)
etc.
This document contains the API (Application Programming Interface)
documentation for this project. Documentation for the Python
objects defined by the project is divided into separate pages for each
package, module, and class. The API documentation also includes two
pages containing information about the project as a whole: a trees
page, and an index page.
Object Documentation
Each Package Documentation page contains:
A description of the package.
A list of the modules and sub-packages contained by the
package.
A summary of the classes defined by the package.
A summary of the functions defined by the package.
A summary of the variables defined by the package.
A detailed description of each function defined by the
package.
A detailed description of each variable defined by the
package.
Each Module Documentation page contains:
A description of the module.
A summary of the classes defined by the module.
A summary of the functions defined by the module.
A summary of the variables defined by the module.
A detailed description of each function defined by the
module.
A detailed description of each variable defined by the
module.
Each Class Documentation page contains:
A class inheritance diagram.
A list of known subclasses.
A description of the class.
A summary of the methods defined by the class.
A summary of the instance variables defined by the class.
A summary of the class (static) variables defined by the
class.
A detailed description of each method defined by the
class.
A detailed description of each instance variable defined by the
class.
A detailed description of each class (static) variable defined
by the class.
Project Documentation
The Trees page contains the module and class hierarchies:
The module hierarchy lists every package and module, with
modules grouped into packages. At the top level, and within each
package, modules and sub-packages are listed alphabetically.
The class hierarchy lists every class, grouped by base
class. If a class has more than one base class, then it will be
listed under each base class. At the top level, and under each base
class, classes are listed alphabetically.
The Index page contains indices of terms and
identifiers:
The term index lists every term indexed by any object's
documentation. For each term, the index provides links to each
place where the term is indexed.
The identifier index lists the (short) name of every package,
module, class, method, function, variable, and parameter. For each
identifier, the index provides a short description, and a link to
its documentation.
The Table of Contents
The table of contents occupies the two frames on the left side of
the window. The upper-left frame displays the project
contents, and the lower-left frame displays the module
contents:
Project Contents...
API Documentation Frame
Module Contents ...
The project contents frame contains a list of all packages
and modules that are defined by the project. Clicking on an entry
will display its contents in the module contents frame. Clicking on a
special entry, labeled "Everything," will display the contents of
the entire project.
The module contents frame contains a list of every
submodule, class, type, exception, function, and variable defined by a
module or package. Clicking on an entry will display its
documentation in the API documentation frame. Clicking on the name of
the module, at the top of the frame, will display the documentation
for the module itself.
The "frames" and "no frames" buttons below the top
navigation bar can be used to control whether the table of contents is
displayed or not.
The Navigation Bar
A navigation bar is located at the top and bottom of every page.
It indicates what type of page you are currently viewing, and allows
you to go to related pages. The following table describes the labels
on the navigation bar. Note that not some labels (such as
[Parent]) are not displayed on all pages.
Label
Highlighted when...
Links to...
[Parent]
(never highlighted)
the parent of the current package
[Package]
viewing a package
the package containing the current object
[Module]
viewing a module
the module containing the current object
[Class]
viewing a class
the class containing the current object
[Trees]
viewing the trees page
the trees page
[Index]
viewing the index page
the index page
[Help]
viewing the help page
the help page
The "show private" and "hide private" buttons below
the top navigation bar can be used to control whether documentation
for private objects is displayed. Private objects are usually defined
as objects whose (short) names begin with a single underscore, but do
not end with an underscore. For example, "_x",
"__pprint", and "epydoc.epytext._tokenize"
are private objects; but "re.sub",
"__init__", and "type_" are not. However,
if a module defines the "__all__" variable, then its
contents are used to decide which objects are private.
A timestamp below the bottom navigation bar indicates when each
page was last updated.
All features of xmpppy library contained within separate modules. At
present there are modules: simplexml - XML handling routines protocol -
jabber-objects (I.e. JID and different stanzas and sub-stanzas) handling
routines. debug - Jacob Lundquist's debugging module. Very handy if you
like colored debug. auth - Non-SASL and SASL stuff. You will need it to
auth as a client or transport. transports - low level connection
handling. TCP and TLS currently. HTTP support planned. roster - simple
roster for use in clients. dispatcher - decision-making logic. Handles
all hooks. The first who takes control over fresh stanzas. features -
different stuff that didn't worths separating into modules browser -
DISCO server framework. Allows to build dynamic disco tree. filetransfer
- Currently contains only IBB stuff. Can be used for bot-to-bot
transfers.
Most of the classes that is defined in all these modules is an
ancestors of class PlugIn so they share a single set of methods allowing
you to compile a featured XMPP client. For every instance of PlugIn class
the 'owner' is the class in what the plug was plugged. While plugging in
such instance usually sets some methods of owner to it's own ones for
easy access. All session specific info stored either in instance of
PlugIn or in owner's instance. This is considered unhandy and there are
plans to port 'Session' class from xmppd.py project for storing all
session-related info. Though if you are not accessing instances variables
directly and use only methods for access all values you should not have
any problems.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
Browser module provides DISCO server framework for your application.
This functionality can be used for very different purposes - from
publishing software version and supported features to building of
"jabber site" that users can navigate with their disco browsers
and interact with active content.
Such functionality is achieved via registering "DISCO
handlers" that are automatically called when user requests some node
of your disco tree.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
Provides PlugIn class functionality to develop extentions for xmpppy.
Also provides Client and Component classes implementations as the
examples of xmpppy structures usage. These classes can be used for simple
applications "AS IS" though.
This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library. It depends on a DISCO browser manager.
There are 3 classes here, a command processor Commands like the Browser, and a command template plugin Command, and an example command.
To use this module:
Instansiate the module with the parent transport and disco browser manager as parameters.
'Plug in' commands using the command template.
The command feature must be added to existing disco replies where neccessary.
What it supplies:
Automatic command registration with the disco browser manager.
Automatic listing of commands in the public command list.
A means of handling requests, by redirection though the command manager.
Commands
Commands is an ancestor of PlugIn and can be attached to any session.
Command_Handler_Prototype
This is a prototype command handler, as each command uses a disco method
and execute method you can implement it any way you like, however this is
my first attempt at making a generic handler that you can hang process
stages on too.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
Other modules can always define extra debug flags for local usage, as
long as they make sure they append them to debug_flags
Also its always a good thing to prefix local flags with something, to
reduce risk of coliding flags. Nothing breaks if two flags would be
identical, but it might activate unintended debugging.
flags can be numeric, but that makes analysing harder, on creation its
not obvious what is activated, and when flag_show is given, output isnt
really meaningfull.
This Debug class can either be initialized and used on app level, or
used independantly by the individual classes.
For samples of usage, see samples subdir in distro source, and
selftest in this code
Value:
'1.4.0'
color_white
Define your flags in yor modules like this:
from debug import *
DBG_INIT = 'init' ; debug_flags.append( DBG_INIT )
DBG_CONNECTION = 'connection' ; debug_flags.append( DBG_CONNECTION )
The reason for having a double statement wis so we can validate params
and catch all undefined debug flags
This gives us control over all used flags, and makes it easier to allow
global debugging in your code, just do something like
foo = Debug( debug_flags )
group flags, that is a flag in it self containing multiple flags should be
defined without the debug_flags.append() sequence, since the parts are already
in the list, also they must of course be defined after the flags they depend on ;)
example:
DBG_MULTI = [ DBG_INIT, DBG_CONNECTION ]
NoDebug
-------
To speed code up, typically for product releases or such
use this class instead if you globaly want to disable debugging
Main xmpppy mechanism. Provides library with methods to assign
different handlers to different XMPP stanzas. Contains one tunable
attribute: DefaultTimeout (25 seconds by default). It defines time that
Dispatcher.SendAndWaitForResponce method will wait for reply stanza
before giving up.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
This module contains variable stuff that is not worth splitting into separate modules.
Here is:
DISCO client and agents-to-DISCO and browse-to-DISCO emulators.
IBR and password manager.
jabber:iq:privacy methods
All these methods takes 'disp' first argument that should be already connected
(and in most cases already authorised) dispatcher instance.
Try to obtain info from the remote object. If remote object doesn't
support disco fall back to browse (if fb2b is true) and if it doesnt
support browse (or fb2b is not true) fall back to agents protocol (if
gb2a is true). Returns obtained info. Used internally.
Gets registration form from remote host. You can pre-fill the info
dictionary. F.e. if you are requesting info on registering user joey than
specify info as {'username':'joey'}. See JEP-0077 for details. 'disp'
must be connected dispatcher instance.
Perform registration on remote server with provided info. disp must be
connected dispatcher instance. Returns true or false depending on
registration result. If registration fails you can get additional info
from the dispatcher's owner attributes lastErrNode, lastErr and
lastErrCode.
Changes password on specified or current (if not specified) server.
disp must be connected and authorized dispatcher instance. Returns true
on success.
Set the ruleset. 'list' should be the simpleXML node formatted
according to RFC 3921 (XMPP-IM) (I.e.
Node('list',{'name':listname},payload=[...]) ) Returns true on
success.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
This module contains IBB class that is the simple implementation of
JEP-0047. Note that this is just a transport for data. You have to
negotiate data transfer before (via StreamInitiation most probably).
Unfortunately SI is not implemented yet.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
'''bad-format -- -- -- The entity has sent XML that cannot be processed.bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the ...
xmpp_stanza_error_conditions
Value:
'''bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed....
sasl_error_conditions
Value:
'''aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Sect...
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
Session
The Session class instance is used for storing all
session-related info like credentials, socket/xml stream/session
state flags, roster items (in case of client type connection)
etc.
When your handler is called it is getting the session instance as the
first argument. This is the difference from xmpppy 0.1 where you got the
"Client" instance. With Session class you can have
"multi-session" client instead of having one client for each
connection. Is is specifically important when you are writing the
server.
Value:
'$Id'
ERRORS
Value:
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
Simplexml module provides xmpppy library with all needed tools to
handle XML nodes and XML streams. I'm personally using it in many other
separate projects. It is designed to be as standalone as possible.
Converts supplied textual string into XML node. Handy f.e. for reading
configuration file. Raises xml.parser.expat.parsererror if provided
string is not well-formed XML.
Converts supplied textual string into XML node. Survives if xml data
is cutted half way round. I.e. "<html>some text <br>some
more text". Will raise xml.parser.expat.parsererror on misplaced
tags though. F.e. "<b>some text <br>some more
text</b>" will not work.
This module contains the low-level implementations of xmpppy connect
methods or (in other words) transports for xmpp-stanzas. Currently here
is three transports: direct TCP connect - TCPsocket class proxied TCP
connect - HTTPPROXYsocket class (CONNECT proxies) TLS connection - TLS
class. Can be used for SSL connections also.
Transports are stackable so you - f.e. TLS use HTPPROXYsocket or
TCPsocket as more low-level transport.
Also exception 'error' is defined to allow capture of this module
specific exceptions.
{'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['','','The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'],'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['','','The data pro...
Start authentication. Result can be obtained via
"SASL.startsasl" attribute and will be either
"success" or "failure". Note that successfull auth
will take at least two Dispatcher.Process() calls.
WARNING! This class is for components only. It will not work in client mode!
Standart xmpppy class that is ancestor of PlugIn and can be attached
to your application.
All processing will be performed in the handlers registered in the browser
instance. You can register any number of handlers ensuring that for each
node/jid combination only one (or none) handler registered.
You can register static information or the fully-blown function that will
calculate the answer dynamically.
Example of static info (see JEP-0030, examples 13-14):
# cl - your xmpppy connection instance.
b=xmpp.browser.Browser()
b.PlugIn(cl)
items=[]
item={}
item['jid']='catalog.shakespeare.lit'
item['node']='books'
item['name']='Books by and about Shakespeare'
items.append(item)
item={}
item['jid']='catalog.shakespeare.lit'
item['node']='clothing'
item['name']='Wear your literary taste with pride'
items.append(item)
item={}
item['jid']='catalog.shakespeare.lit'
item['node']='music'
item['name']='Music from the time of Shakespeare'
items.append(item)
info={'ids':[], 'features':[]}
b.setDiscoHandler({'items':items,'info':info})
items should be a list of item elements.
every item element can have any of these four keys: 'jid', 'node', 'name', 'action'
info should be a dicionary and must have keys 'ids' and 'features'.
Both of them should be lists:
ids is a list of dictionaries and features is a list of text strings.
Example (see JEP-0030, examples 1-2)
# cl - your xmpppy connection instance.
b=xmpp.browser.Browser()
b.PlugIn(cl)
items=[]
ids=[]
ids.append({'category':'conference','type':'text','name':'Play-Specific Chatrooms'})
ids.append({'category':'directory','type':'chatroom','name':'Play-Specific Chatrooms'})
features=[NS_DISCO_INFO,NS_DISCO_ITEMS,NS_MUC,NS_REGISTER,NS_SEARCH,NS_TIME,NS_VERSION]
info={'ids':ids,'features':features}
# info['xdata']=xmpp.protocol.DataForm() # JEP-0128
b.setDiscoHandler({'items':[],'info':info})
_traversePath(self,
node,
jid,
set=0)
Returns dictionary and key or None,None None - root node (w/o
"node" attribute) /a/b/c - node /a/b/ - branch Set returns
'' or None as the key get returns '' or None as the key or None as
the dict.
Returns dictionary and key or None,None None - root node (w/o
"node" attribute) /a/b/c - node /a/b/ - branch Set returns ''
or None as the key get returns '' or None as the key or None as the dict.
Used internally.
This is the main method that you will use in this class.
It is used to register supplied DISCO handler (or dictionary with static info)
as handler of some disco tree branch.
If you do not specify the node this handler will be used for all queried nodes.
If you do not specify the jid this handler will be used for all queried JIDs.
Usage:
cl.Browser.setDiscoHandler(someDict,node,jid)
or
cl.Browser.setDiscoHandler(someDISCOHandler,node,jid)
where
someDict={
'items':[
{'jid':'jid1','action':'action1','node':'node1','name':'name1'},
{'jid':'jid2','action':'action2','node':'node2','name':'name2'},
{'jid':'jid3','node':'node3','name':'name3'},
{'jid':'jid4','node':'node4'}
],
'info' :{
'ids':[
{'category':'category1','type':'type1','name':'name1'},
{'category':'category2','type':'type2','name':'name2'},
{'category':'category3','type':'type3','name':'name3'},
],
'features':['feature1','feature2','feature3','feature4'],
'xdata':DataForm
}
}
and/or
def someDISCOHandler(session,request,TYR):
# if TYR=='items': # returns items list of the same format as shown above
# elif TYR=='info': # returns info dictionary of the same format as shown above
# else: # this case is impossible for now.
Unregisters DISCO handler that is resonsible for this node/jid
combination. When handler is unregistered the branch is handled in the
same way that it's parent branch from this moment.
Connect to jabber server. If you want to specify different ip/port to
connect to you can pass it as tuple as first parameter. If there is HTTP
proxy between you and server specify it's address and credentials (if
needed) in the second argument. If you want ssl/tls support to be
discovered and enable automatically - leave third argument as None. (ssl
will be autodetected only if port is 5223 or 443) If you want to force
SSL start (i.e. if port 5223 or 443 is remapped to some non-standard
port) then set it to 1. If you want to disable tls/ssl support
completely, set it to 0. Example:
connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})
Returns '' or 'tcp' or 'tls', depending on the result.
Caches server name and (optionally) port to connect to. "debug" parameter specifies
the debug IDs that will go into debug output. You can either specifiy an "include"
or "exclude" list. The latter is done via adding "always" pseudo-ID to the list.
Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket',
'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] .
Default disconnect handler. Just raises an IOError. If you choosed to
use this class in your production client, override this method or at
least unregister it.
connect(self,
server=None,
proxy=None)
This will connect to the server, and if the features tag is found
then set the namespace to be jabber:client as that is required for
jabberd2.
Init function for Components. As components use a different auth
mechanism which includes the namespace of the component. Jabberd1.4 and
Ejabberd use the default namespace then for all client messages. Jabberd2
uses jabber:client. 'server' argument is a server name that you are
connecting to (f.e. "localhost"). 'port' can be specified if
'server' resolves to correct IP. If it is not then you'll need to specify
IP and port while calling "connect()".
This will connect to the server, and if the features tag is found then
set the namespace to be jabber:client as that is required for jabberd2.
'server' and 'proxy' arguments have the same meaning as in
xmpp.Client.connect()
This is a prototype command handler, as each command uses a disco method
and execute method you can implement it any way you like, however this is
my first attempt at making a generic handler that you can hang process
stages on too. There is an example command below.
The parameters are as follows:
name : the name of the command within the jabber environment
description : the natural language description
discofeatures : the features supported by the command
initial : the initial command in the from of {'execute':commandname}
All stages set the 'actions' dictionary for each session to represent the possible options available.
Commands is an ancestor of PlugIn and can be attached to any session.
The commands class provides a lookup and browse mechnism. It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands, it adds the 'list' disco type to your existing disco handler function.
How it works:
The commands are added into the existing Browser on the correct nodes. When the command list is built the supplied discovery handler function needs to have a 'list' option in type. This then gets enumerated, all results returned as None are ignored.
The command executed is then called using it's Execute method. All session management is handled by the command itself.
addCommand(self,
name,
cmddisco,
cmdexecute,
jid='')
The method to call if adding a new command to the session, the
requred parameters of cmddisco and cmdexecute are the methods to
enable that command to be executed
Example class. You should read source if you wish to understate how it
works. Generally, it presents a "master" that giudes user
through to calculate something.
flag can be of folowing types:
None - this msg will always be shown if any debugging is on
flag - will be shown if flag is active
(flag1,flag2,,,) - will be shown if any of the given flags
are active
if prefix / sufix are not given, default ones from init will be used
lf = -1 means strip linefeed if pressent
lf = 1 means add linefeed if not pressent
Restores user-registered callbacks structure from dump previously
obtained via dumpHandlers. Used within the library to carry user handlers
set over Dispatcher replugins.
Check incoming stream for data waiting. If "timeout" is
positive - block for as max. this time. Returns: 1) length of processed
data if some data were processed; 2) '0' string if no data were processed
but link is alive; 3) 0 (zero) if underlying connection is closed. Take
note that in case of disconnection detect during Process() call
disconnect handlers are called automatically.
Creates internal structures for newly registered namespace. You can
register handlers for this namespace afterwards. By default one namespace
already registered (jabber:client or jabber:component:accept depending on
context.
RegisterProtocol(self,
tag_name,
Proto,
xmlns=None,
order='info')
Used to declare some top-level stanza name to dispatcher. Needed to
start registering handlers for such stanzas. Iq, message and presence
protocols are registered by default.
Register user callback as stanzas handler of declared type. Callback must take
(if chained, see later) arguments: dispatcher instance (for replying), incomed
return of previous handlers.
The callback must raise xmpp.NodeProcessed just before return if it want preven
callbacks to be called with the same stanza as argument _and_, more importantly
library from returning stanza to sender with error set (to be enabled in 0.2 ve
Arguments:
"name" - name of stanza. F.e. "iq".
"handler" - user callback.
"typ" - value of stanza's "type" attribute. If not specified any value match
"ns" - namespace of child that stanza must contain.
"chained" - chain together output of several handlers.
"makefirst" - insert handler in the beginning of handlers list instead of
adding it to the end. Note that more common handlers (i.e. w/o "typ" and "
will be called first nevertheless.
"system" - call handler even if NodeProcessed Exception were raised already.
Raise some event. Takes three arguments: 1) "realm" - scope
of event. Usually a namespace. 2) "event" - the event itself.
F.e. "SUCESSFULL SEND". 3) data that comes along with event.
Depends on event.
Block and wait until stanza with specific "id" attribute
will come. If no such stanza is arrived within timeout, return None. If
operation failed for some reason then owner's attributes lastErrNode,
lastErr and lastErrCode are set accordingly.
IBB used to transfer small-sized data chunk over estabilished xmpp
connection. Data is split into small blocks (by default 3000 bytes each),
encoded as base 64 and sent to another entity that compiles these blocks
back into the data chunk. This is very inefficiend but should work under
any circumstances. Note that using IBB normally should be the last
resort.
Start new stream. You should provide stream id 'sid', the endpoind jid
'to', the file object containing info for send 'fp'. Also the desired
blocksize can be specified. Take into account that recommended stanza
size is 4k and IBB uses base64 encoding that increases size of data by
1/3.
Handle remote side reply about is it agree or not to receive our
datastream. Used internally. Raises xmpppy event specfiying if the data
transfer is agreed upon.
This class is used in the DataForm class to describe the single data
item. If you are working with jabber:x:data (XEP-0004, XEP-0068,
XEP-0122) then you will need to work with instances of this class.
__init__(self,
name=None,
value=None,
typ=None,
required=0,
label=None,
desc=None,
options=[],
node=None)
Create new data field of specified name,value and type.
Create new data field of specified name,value and type. Also
'required','desc' and 'options' fields can be set. Alternatively other
XML object can be passed in as the 'node' parameted to replicate it as a
new datafiled.
Create new dataform of type 'typ'. 'data' is the list of DataField
instances that this dataform contains, 'title' - the title string. You
can specify the 'node' argument as the other node to be used as base for
constructing this dataform.
title and instructions is optional and SHOULD NOT contain newlines.
Several instructions MAY be present. 'typ' can be one of ('form' |
'submit' | 'cancel' | 'result' ) 'typ' of reply iq can be ( 'result' |
'set' | 'set' | 'result' ) respectively. 'cancel' form can not contain
any fields. All other forms contains AT LEAST one field. 'title' MAY be
included in forms of type "form" and "result"
Create error reply basing on the received 'node' stanza and the
'error' error condition. If the 'node' is not the received stanza but
locally created ('to' and 'from' fields needs not swapping) specify the
'reply' argument as false.
Create new error node object. Mandatory parameter: name - name of
error condition. Optional parameters: code, typ, text. Used for backwards
compartibility with older jabber protocol.
Create Iq object. You can specify type, query namespace any additional
attributes, recipient of the iq, sender of the iq, any additional payload
(f.e. jabber:x:data node) and namespace in one go. Alternatively you can
pass in the other XML object as the 'node' parameted to replicate it as
an iq.
Constructor. JID can be specified as string (jid argument) or as
separate parts. Examples: JID('node@domain/resource')
JID(node='node',domain='domain.org')
Create message object. You can specify recipient, text of message,
type of message any additional attributes, sender of the message, any
additional payload (f.e. jabber:x:delay element) and namespace in one go.
Alternatively you can pass in the other XML object as the 'node'
parameted to replicate it as message.
Create presence object. You can specify recipient, type of message,
priority, show and status values any additional attributes, sender of the
presence, timestamp, any additional payload (f.e. jabber:x:delay element)
and namespace in one go. Alternatively you can pass in the other XML
object as the 'node' parameted to replicate it as presence.
__init__(self,
name=None,
to=None,
typ=None,
frm=None,
attrs={},
payload=[],
timestamp=None,
xmlns=None,
node=None)
Constructor, name is the name of the stanza i.e.
Constructor, name is the name of the stanza i.e. 'message' or
'presence' or 'iq'. to is the value of 'to' attribure, 'typ' - 'type'
attribute frn - from attribure, attrs - other attributes mapping, payload
- same meaning as for simplexml payload definition timestamp - the time
value that needs to be stamped over stanza xmlns - namespace of top
stanza node node - parsed or unparsed stana to be taken as prototype.
Defines a plenty of methods that will allow you to manage roster. Also
automatically track presences from remote JIDs taking into account that
every JID can have multiple resources connected. Does not currently
support 'error' presences. You can also use mapping interface for access
to the internal representation of contacts in roster.
Register presence and subscription trackers in the owner's dispatcher.
Also request roster from server if the 'request' argument is set. Used
internally.
The Session class instance is used for storing all session-related
info like credentials, socket/xml stream/session state flags, roster
items (in case of client type connection) etc. Session object have no
means of discovering is any info is ready to be read. Instead you should
use poll() (recomended) or select() methods for this purpose. Session can
be one of two types: 'server' and 'client'. 'server' session handles
inbound connection and 'client' one used to create an outbound one.
Session instance have multitude of internal attributes. The most imporant
is the 'peer' one. It is set once the peer is authenticated (client).
push_queue(self,
failreason='urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable')
If stream is authenticated than move items from "send"
queue to "immidiatedly send" queue.
_dispatch(self,
stanza,
trusted=0)
This is callback that is used to pass the received stanza forth to
owner's dispatcher _if_ the stream is authorised.
set_stream_state(self,
newstate)
Change the underlaying XML stream state Stream starts with
STREAM__NOT_OPENED and then proceeds with STREAM__OPENED,
STREAM__CLOSING and STREAM__CLOSED states.
When the session is created it's type (client/server) is determined
from the beginning. socket argument is the pre-created socket-like
object. It must have the following methods: send, recv, fileno, close.
owner is the 'master' instance that have Dispatcher plugged into it and
generally will take care about all session events. xmlns is the stream
namespace that will be used. Client must set this argument If server sets
this argument than stream will be dropped if opened with some another
namespace. peer is the name of peer instance. This is the flag that
differentiates client session from server session. Client must set it to
the name of the server that will be connected, server must leave this
argument alone.
This method is used to initialise the internal xml expat parser and to
send initial stream header (in case of client connection). Should be used
after initial connection and after every stream restart.
Put chunk into "immidiatedly send" queue. Should only be
used for auth/TLS stuff and like. If you just want to shedule regular
stanza for delivery use enqueue method.
Takes Protocol instance as argument. Puts stanza into "send"
fifo queue. Items into the send queue are hold until stream
authenticated. After that this method is effectively the same as
"sendnow" method.
If stream is authenticated than move items from "send" queue
to "immidiatedly send" queue. Else if the stream is failed then
return all queued stanzas with error passed as argument. Otherwise do
nothing.
This is callback that is used to pass the received stanza forth to
owner's dispatcher _if_ the stream is authorised. Otherwise the stanza is
just dropped. The 'trusted' argument is used to emulate stanza receive.
This method is used internally.
This callback is used to handle opening stream tag of the incoming
stream. In the case of client session it just make some validation.
Server session also sends server headers and if the stream valid the
features node. Used internally.
Notify the peer about stream closure. Ensure that xmlstream is not
brokes - i.e. if the stream isn't opened yet - open it before closure. If
the error condition is specified than create a stream error and send it
along with closing stream tag. Emulate receiving 'unavailable' type
presence just before stream closure.
Change the session state. Session starts with SESSION_NOT_AUTHED state
and then comes through SESSION_AUTHED, SESSION_BOUND, SESSION_OPENED and
SESSION_CLOSED states.
Change the underlaying XML stream state Stream starts with
STREAM__NOT_OPENED and then proceeds with STREAM__OPENED, STREAM__CLOSING
and STREAM__CLOSED states. Note that some features (like TLS and SASL)
requires stream re-start so this state can have non-linear changes.
Node class describes syntax of separate XML Node. It have a
constructor that permits node creation from set of "namespace
name", attributes and payload of text strings and other nodes. It
does not natively support building node from text string and uses
NodeBuilder class for that purpose. After creation node can be mangled in
many ways so it can be completely changed. Also node can be serialised
into string in one of two modes: default (where the textual
representation of node describes it exactly) and "fancy" - with
whitespace added to make indentation and thus make result more readable
by human.
Node class have attribute FORCE_NODE_RECREATION that is defaults to
False thus enabling fast node replication from the some other node. The
drawback of the fast way is that new node shares some info with the
"original" node that is changing the one node may influence the
other. Though it is rarely needed (in xmpppy it is never needed at all
since I'm usually never using original node after replication (and using
replication only to move upwards on the classes tree).
__init__(self,
tag=None,
attrs={},
payload=[],
parent=None,
node=None)
Takes "tag" argument as the name of node (prepended by
namespace, if needed and separated from it by a space), attrs
dictionary as the set of arguments, payload list as the set of
textual strings and child nodes that this node carries within itself
and "parent" argument that is another node that this one
will be the child of.
setTag(self,
name,
attrs={},
namespace=None)
Same as getTag but if the node with specified namespace/attributes
not found, creates such node and returns it.
setTagData(self,
tag,
val,
attrs={})
Creates new node (if not already present) with name
"tag" and (optionally) attributes "attrs" and
sets it's CDATA to string "val".
Takes "tag" argument as the name of node (prepended by
namespace, if needed and separated from it by a space), attrs dictionary
as the set of arguments, payload list as the set of textual strings and
child nodes that this node carries within itself and "parent"
argument that is another node that this one will be the child of. Also
the __init__ can be provided with "node" argument that is
either a text string containing exactly one node or another Node instance
to begin with. If both "node" and other arguments is provided
then the node initially created as replica of "node" provided
and then modified to be compliant with other arguments.
Deletes the "node" from the node's childs list, if
"node" is an instance. Else deletes the first node that have
specified name and (optionally) attributes.
Return the payload of node i.e. list of child nodes and CDATA entries.
F.e. for "<node>text1<nodea/><nodeb/>
text2</node>" will be returned list: ['text1', <nodea
instance>, <nodeb instance>, ' text2'].
Sets node's parent to "node". WARNING: do not checks if the
parent already present and not removes the node from the list of childs
of previous parent.
Sets node payload according to the list specified. WARNING: completely
replaces all node's previous content. If you wish just to add child or
CDATA - use addData or addChild methods.
Takes two optional parameters: "data" and
"initial_node". By default class initialised with empty Node
class instance. Though, if "initial_node" is provided it used
as "starting point". You can think about it as of "node
upgrade". "data" (if provided) feeded to parser
immidiatedly after instance init.
Gets called when the NodeBuilder reaches some level of depth on it's
way up with the built node as argument. Can be redefined to convert
incoming XML stanzas to program events.
HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base
class redefines only connect method. Allows to use HTTP proxies like
squid with (optionally) simple authentication (using login and
password).
Caches proxy and target addresses. 'proxy' argument is a dictionary
with mandatory keys 'host' and 'port' (proxy address) and optional keys
'user' and 'password' to use for authentication. 'server' argument is a
tuple of host and port - just like TCPsocket uses.
Starts connection. Connects to proxy, supplies login and password to
it (if were specified while creating instance). Instructs proxy to make
connection to the target server. Returns non-empty sting on success.
If the 'now' argument is true then starts using encryption
immidiatedly. If 'now' in false then starts encryption as soon as TLS
feature is declared by the server (if it were already declared - it is
ok).
Unregisters TLS handler's from owner's dispatcher. Take note that
encription can not be stopped once started. You can only break the
connection and start over.
1## transports.py 2## 3## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: transports.py,v 1.31 2007/09/15 11:34:28 normanr Exp $ 16 17""" 18This module contains the low-level implementations of xmpppy connect methods or 19(in other words) transports for xmpp-stanzas. 20Currently here is three transports: 21direct TCP connect - TCPsocket class 22proxied TCP connect - HTTPPROXYsocket class (CONNECT proxies) 23TLS connection - TLS class. Can be used for SSL connections also. 24 25Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as more low-level transport. 26 27Also exception 'error' is defined to allow capture of this module specific exceptions. 28""" 29 30importsocket,select,base64,dispatcher,sys 31fromsimplexmlimportustr 32fromclientimportPlugIn 33fromprotocolimport* 34 35# determine which DNS resolution library is available 36HAVE_DNSPYTHON=False 37HAVE_PYDNS=False 38try: 39importdns.resolver# http://dnspython.org/ 40HAVE_DNSPYTHON=True 41exceptImportError: 42try: 43importDNS# http://pydns.sf.net/ 44HAVE_PYDNS=True 45exceptImportError: 46#TODO: use self.DEBUG() 47sys.stderr.write("Could not load one of the supported DNS libraries (dnspython or pydns). SRV records will not be queried and you may need to set custom hostname/port for some servers to be accessible.\n") 48 49DATA_RECEIVED='DATA RECEIVED' 50DATA_SENT='DATA SENT' 51
66""" Cache connection point 'server'. 'server' is the tuple of (host, port) 67 absolutely the same as standard tcp socket uses. """ 68PlugIn.__init__(self) 69self.DBG_LINE='socket' 70self._exported_methods=[self.send,self.disconnect] 71 72# SRV resolver 73ifuse_srvand(HAVE_DNSPYTHONorHAVE_PYDNS): 74host,port=server 75possible_queries=['_xmpp-client._tcp.'+host] 76 77forqueryinpossible_queries: 78try: 79ifHAVE_DNSPYTHON: 80answers=[xforxindns.resolver.query(query,'SRV')] 81ifanswers: 82host=str(answers[0].target) 83port=int(answers[0].port) 84break 85elifHAVE_PYDNS: 86# ensure we haven't cached an old configuration 87DNS.ParseResolvConf() 88response=DNS.Request().req(query,qtype='SRV') 89answers=response.answers 90iflen(answers)>0: 91# ignore the priority and weight for now 92_,_,port,host=answers[0]['data'] 93del_ 94port=int(port) 95break 96except: 97#TODO: use self.DEBUG() 98print'An error occurred while looking up %s'%query 99server=(host,port)100# end of SRV resolver101102self._server=server
105""" Fire up connection. Return non-empty string on success.106 Also registers self.disconnected method in the owner's dispatcher.107 Called internally. """108ifnotself._server:self._server=(self._owner.Server,5222)109ifnotself.connect(self._server):return110self._owner.Connection=self111self._owner.RegisterDisconnectHandler(self.disconnected)112return'ok'
122""" Try to connect. Returns non-empty string on success. """123try:124ifnotserver:server=self._server125self._sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)126self._sock.connect((server[0],int(server[1])))127self._send=self._sock.sendall128self._recv=self._sock.recv129self.DEBUG("Successfully connected to remote host %s"%`server`,'start')130return'ok'131exceptsocket.error,(errno,strerror):132self.DEBUG("Failed to connect to remote host %s: %s (%s)"%(`server`,strerror,errno),'error')133except:pass
136""" Disconnect from the remote server and unregister self.disconnected method from137 the owner's dispatcher. """138self._sock.close()139ifself._owner.__dict__.has_key('Connection'):140delself._owner.Connection141self._owner.UnregisterDisconnectHandler(self.disconnected)
144""" Reads all pending incoming data.145 In case of disconnection calls owner's disconnected() method and then raises IOError exception."""146try:received=self._recv(BUFLEN)147exceptsocket.sslerror,e:148self._seen_data=0149ife[0]==socket.SSL_ERROR_WANT_READ:return''150ife[0]==socket.SSL_ERROR_WANT_WRITE:return''151self.DEBUG('Socket error while receiving data','error')152sys.exc_clear()153self._owner.disconnected()154raiseIOError("Disconnected from server")155except:received=''156157whileself.pending_data(0):158try:add=self._recv(BUFLEN)159except:add=''160received+=add161ifnotadd:break162163iflen(received):# length of 0 means disconnect164self._seen_data=1165self.DEBUG(received,'got')166ifhasattr(self._owner,'Dispatcher'):167self._owner.Dispatcher.Event('',DATA_RECEIVED,received)168else:169self.DEBUG('Socket error while receiving data','error')170self._owner.disconnected()171raiseIOError("Disconnected from server")172returnreceived
175""" Writes raw outgoing data. Blocks until done.176 If supplied data is unicode string, encodes it to utf-8 before send."""177iftype(raw_data)==type(u''):raw_data=raw_data.encode('utf-8')178eliftype(raw_data)<>type(''):raw_data=ustr(raw_data).encode('utf-8')179try:180self._send(raw_data)181# Avoid printing messages that are empty keepalive packets.182ifraw_data.strip():183self.DEBUG(raw_data,'sent')184self._owner.Dispatcher.Event('',DATA_SENT,raw_data)185except:186self.DEBUG("Socket error while sending data",'error')187self._owner.disconnected()
205""" HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class206 redefines only connect method. Allows to use HTTP proxies like squid with207 (optionally) simple authentication (using login and password). """
209""" Caches proxy and target addresses.210 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)211 and optional keys 'user' and 'password' to use for authentication.212 'server' argument is a tuple of host and port - just like TCPsocket uses. """213TCPsocket.__init__(self,server,use_srv)214self.DBG_LINE=DBG_CONNECT_PROXY215self._proxy=proxy
218""" Starts connection. Used interally. Returns non-empty string on success."""219owner.debug_flags.append(DBG_CONNECT_PROXY)220returnTCPsocket.plugin(self,owner)
266""" If the 'now' argument is true then starts using encryption immidiatedly.267 If 'now' in false then starts encryption as soon as TLS feature is268 declared by the server (if it were already declared - it is ok).269 """270ifowner.__dict__.has_key('TLS'):return# Already enabled.271PlugIn.PlugIn(self,owner)272DBG_LINE='TLS'273ifnow:returnself._startSSL()274ifself._owner.Dispatcher.Stream.features:275try:self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)276exceptNodeProcessed:pass277else:self._owner.RegisterHandlerOnce('features',self.FeaturesHandler,xmlns=NS_STREAMS)278self.starttls=None
288""" Used to analyse server <features/> tag for TLS support.289 If TLS is supported starts the encryption negotiation. Used internally"""290ifnotfeats.getTag('starttls',namespace=NS_TLS):291self.DEBUG("TLS unsupported by remote server.",'warn')292return293self.DEBUG("TLS supported by remote server. Requesting TLS start.",'ok')294self._owner.RegisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS)295self._owner.RegisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS)296self._owner.Connection.send('<starttls xmlns="%s"/>'%NS_TLS)297raiseNodeProcessed
300""" Returns true if there possible is a data ready to be read. """301returnself._tcpsock._seen_dataorselect.select([self._tcpsock._sock],[],[],timeout)[0]
304""" Immidiatedly switch socket to TLS mode. Used internally."""305""" Here we should switch pending_data to hint mode."""306tcpsock=self._owner.Connection307tcpsock._sslObj=socket.ssl(tcpsock._sock,None,None)308tcpsock._sslIssuer=tcpsock._sslObj.issuer()309tcpsock._sslServer=tcpsock._sslObj.server()310tcpsock._recv=tcpsock._sslObj.read311tcpsock._send=tcpsock._sslObj.write312313tcpsock._seen_data=1314self._tcpsock=tcpsock315tcpsock.pending_data=self.pending_data316tcpsock._sock.setblocking(0)317318self.starttls='success'
321""" Handle server reply if TLS is allowed to process. Behaves accordingly.322 Used internally."""323ifstarttls.getNamespace()<>NS_TLS:return324self.starttls=starttls.getName()325ifself.starttls=='failure':326self.DEBUG("Got starttls response: "+self.starttls,'error')327return328self.DEBUG("Got starttls proceed response. Switching to TLS/SSL...",'ok')329self._startSSL()330self._owner.Dispatcher.PlugOut()331dispatcher.Dispatcher().PlugIn(self._owner)
1## $Id: commands.py,v 1.17 2007/08/28 09:54:15 normanr Exp $ 2 3## Ad-Hoc Command manager 4## Mike Albon (c) 5th January 2005 5 6## This program is free software; you can redistribute it and/or modify 7## it under the terms of the GNU General Public License as published by 8## the Free Software Foundation; either version 2, or (at your option) 9## any later version. 10## 11## This program is distributed in the hope that it will be useful, 12## but WITHOUT ANY WARRANTY; without even the implied warranty of 13## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14## GNU General Public License for more details. 15 16 17"""This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library. It depends on a DISCO browser manager. 18 19There are 3 classes here, a command processor Commands like the Browser, and a command template plugin Command, and an example command. 20 21To use this module: 22 23 Instansiate the module with the parent transport and disco browser manager as parameters. 24 'Plug in' commands using the command template. 25 The command feature must be added to existing disco replies where neccessary. 26 27What it supplies: 28 29 Automatic command registration with the disco browser manager. 30 Automatic listing of commands in the public command list. 31 A means of handling requests, by redirection though the command manager. 32""" 33 34fromprotocolimport* 35fromclientimportPlugIn 36
38"""Commands is an ancestor of PlugIn and can be attached to any session. 39 40 The commands class provides a lookup and browse mechnism. It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands, it adds the 'list' disco type to your existing disco handler function. 41 42 How it works: 43 The commands are added into the existing Browser on the correct nodes. When the command list is built the supplied discovery handler function needs to have a 'list' option in type. This then gets enumerated, all results returned as None are ignored. 44 The command executed is then called using it's Execute method. All session management is handled by the command itself. 45 """
47"""Initialises class and sets up local variables""" 48PlugIn.__init__(self) 49DBG_LINE='commands' 50self._exported_methods=[] 51self._handlers={'':{}} 52self._browser=browser
70"""The internal method to process the routing of command execution requests""" 71# This is the command handler itself. 72# We must: 73# Pass on command execution to command handler 74# (Do we need to keep session details here, or can that be done in the command?) 75jid=str(request.getTo()) 76try: 77node=request.getTagAttr('command','node') 78except: 79conn.send(Error(request,ERR_BAD_REQUEST)) 80raiseNodeProcessed 81ifself._handlers.has_key(jid): 82ifself._handlers[jid].has_key(node): 83self._handlers[jid][node]['execute'](conn,request) 84else: 85conn.send(Error(request,ERR_ITEM_NOT_FOUND)) 86raiseNodeProcessed 87elifself._handlers[''].has_key(node): 88self._handlers[''][node]['execute'](conn,request) 89else: 90conn.send(Error(request,ERR_ITEM_NOT_FOUND)) 91raiseNodeProcessed
94"""The internal method to process service discovery requests""" 95# This is the disco manager handler. 96iftyp=='items': 97# We must: 98# Generate a list of commands and return the list 99# * This handler does not handle individual commands disco requests.100# Pseudo:101# Enumerate the 'item' disco of each command for the specified jid102# Build responce and send103# To make this code easy to write we add an 'list' disco type, it returns a tuple or 'none' if not advertised104list=[]105items=[]106jid=str(request.getTo())107# Get specific jid based results108ifself._handlers.has_key(jid):109foreachinself._handlers[jid].keys():110items.append((jid,each))111else:112# Get generic results113foreachinself._handlers[''].keys():114items.append(('',each))115ifitems!=[]:116foreachinitems:117i=self._handlers[each[0]][each[1]]['disco'](conn,request,'list')118ifi!=None:119list.append(Node(tag='item',attrs={'jid':i[0],'node':i[1],'name':i[2]}))120iq=request.buildReply('result')121ifrequest.getQuerynode():iq.setQuerynode(request.getQuerynode())122iq.setQueryPayload(list)123conn.send(iq)124else:125conn.send(Error(request,ERR_ITEM_NOT_FOUND))126raiseNodeProcessed127eliftyp=='info':128return{'ids':[{'category':'automation','type':'command-list'}],'features':[]}
131"""The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed"""132# This command takes a command object and the name of the command for registration133# We must:134# Add item into disco135# Add item into command list136ifnotself._handlers.has_key(jid):137self._handlers[jid]={}138self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid=jid)139ifself._handlers[jid].has_key(name):140raiseNameError,'Command Exists'141else:142self._handlers[jid][name]={'disco':cmddisco,'execute':cmdexecute}143# Need to add disco stuff here144self._browser.setDiscoHandler(cmddisco,node=name,jid=jid)
147"""Removed command from the session"""148# This command takes a command object and the name used for registration149# We must:150# Remove item from disco151# Remove item from command list152ifnotself._handlers.has_key(jid):153raiseNameError,'Jid not found'154ifnotself._handlers[jid].has_key(name):155raiseNameError,'Command not found'156else:157#Do disco removal here158command=self.getCommand(name,jid)['disco']159delself._handlers[jid][name]160self._browser.delDiscoHandler(command,node=name,jid=jid)
163"""Returns the command tuple"""164# This gets the command object with name165# We must:166# Return item that matches this name167ifnotself._handlers.has_key(jid):168raiseNameError,'Jid not found'169elifnotself._handlers[jid].has_key(name):170raiseNameError,'Command not found'171else:172returnself._handlers[jid][name]
175"""This is a prototype command handler, as each command uses a disco method 176 and execute method you can implement it any way you like, however this is 177 my first attempt at making a generic handler that you can hang process 178 stages on too. There is an example command below.179180 The parameters are as follows:181 name : the name of the command within the jabber environment182 description : the natural language description183 discofeatures : the features supported by the command184 initial : the initial command in the from of {'execute':commandname}185186 All stages set the 'actions' dictionary for each session to represent the possible options available.187 """188name='examplecommand'189count=0190description='an example command'191discofeatures=[NS_COMMANDS,NS_DATA]192# This is the command template
194"""Set up the class"""195PlugIn.__init__(self)196DBG_LINE='command'197self.sessioncount=0198self.sessions={}199# Disco information for command list pre-formatted as a tuple200self.discoinfo={'ids':[{'category':'automation','type':'command-node','name':self.description}],'features':self.discofeatures}201self._jid=jid
204"""Plug command into the commands class"""205# The owner in this instance is the Command Processor206self._commands=owner207self._owner=owner._owner208self._commands.addCommand(self.name,self._DiscoHandler,self.Execute,jid=self._jid)
220"""The method that handles all the commands, and routes them to the correct method for that stage."""221# New request or old?222try:223session=request.getTagAttr('command','sessionid')224except:225session=None226try:227action=request.getTagAttr('command','action')228except:229action=None230ifaction==None:action='execute'231# Check session is in session list232ifself.sessions.has_key(session):233ifself.sessions[session]['jid']==request.getFrom():234# Check action is vaild235ifself.sessions[session]['actions'].has_key(action):236# Execute next action237self.sessions[session]['actions'][action](conn,request)238else:239# Stage not presented as an option240self._owner.send(Error(request,ERR_BAD_REQUEST))241raiseNodeProcessed242else:243# Jid and session don't match. Go away imposter244self._owner.send(Error(request,ERR_BAD_REQUEST))245raiseNodeProcessed246elifsession!=None:247# Not on this sessionid you won't.248self._owner.send(Error(request,ERR_BAD_REQUEST))249raiseNodeProcessed250else:251# New session252self.initial[action](conn,request)
255"""The handler for discovery events"""256iftype=='list':257return(request.getTo(),self.name,self.description)258eliftype=='items':259return[]260eliftype=='info':261returnself.discoinfo
264""" Example class. You should read source if you wish to understate how it works. 265 Generally, it presents a "master" that giudes user through to calculate something.266 """267name='testcommand'268description='a noddy example command'
275""" Determine """276# This is the only place this should be repeated as all other stages should have SessionIDs277try:278session=request.getTagAttr('command','sessionid')279except:280session=None281ifsession==None:282session=self.getSessionID()283self.sessions[session]={'jid':request.getFrom(),'actions':{'cancel':self.cmdCancel,'next':self.cmdSecondStage,'execute':self.cmdSecondStage},'data':{'type':None}}284# As this is the first stage we only send a form285reply=request.buildReply('result')286form=DataForm(title='Select type of operation',data=['Use the combobox to select the type of calculation you would like to do, then click Next',DataField(name='calctype',desc='Calculation Type',value=self.sessions[session]['data']['type'],options=[['circlediameter','Calculate the Diameter of a circle'],['circlearea','Calculate the area of a circle']],typ='list-single',required=1)])287replypayload=[Node('actions',attrs={'execute':'next'},payload=[Node('next')]),form]288reply.addChild(name='command',namespace=NS_COMMANDS,attrs={'node':request.getTagAttr('command','node'),'sessionid':session,'status':'executing'},payload=replypayload)289self._owner.send(reply)290raiseNodeProcessed
300reply=request.buildReply('result')301form=DataForm(title='Enter the radius',data=['Enter the radius of the circle (numbers only)',DataField(desc='Radius',name='radius',typ='text-single')])302replypayload=[Node('actions',attrs={'execute':'complete'},payload=[Node('complete'),Node('prev')]),form]303reply.addChild(name='command',namespace=NS_COMMANDS,attrs={'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'executing'},payload=replypayload)304self._owner.send(reply)305raiseNodeProcessed
1## roster.py 2## 3## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: roster.py,v 1.20 2005/07/13 13:22:52 snakeru Exp $ 16 17""" 18Simple roster implementation. Can be used though for different tasks like 19mass-renaming of contacts. 20""" 21 22fromprotocolimport* 23fromclientimportPlugIn 24
26""" Defines a plenty of methods that will allow you to manage roster. 27 Also automatically track presences from remote JIDs taking into 28 account that every JID can have multiple resources connected. Does not 29 currently support 'error' presences. 30 You can also use mapping interface for access to the internal representation of 31 contacts in roster. 32 """
51""" Request roster from server if it were not yet requested 52 (or if the 'force' argument is set). """ 53ifself.setisNone:self.set=0 54elifnotforce:return 55self._owner.send(Iq('get',NS_ROSTER)) 56self.DEBUG('Roster requested from server','start')
59""" Requests roster from server if neccessary and returns self.""" 60ifnotself.set:self.Request() 61whilenotself.set:self._owner.Process(10) 62returnself
65""" Subscription tracker. Used internally for setting items state in 66 internal roster representation. """ 67foriteminstanza.getTag('query').getTags('item'): 68jid=item.getAttr('jid') 69ifitem.getAttr('subscription')=='remove': 70ifself._data.has_key(jid):delself._data[jid] 71raiseNodeProcessed# a MUST 72self.DEBUG('Setting roster item %s...'%jid,'ok') 73ifnotself._data.has_key(jid):self._data[jid]={} 74self._data[jid]['name']=item.getAttr('name') 75self._data[jid]['ask']=item.getAttr('ask') 76self._data[jid]['subscription']=item.getAttr('subscription') 77self._data[jid]['groups']=[] 78ifnotself._data[jid].has_key('resources'):self._data[jid]['resources']={} 79forgroupinitem.getTags('group'):self._data[jid]['groups'].append(group.getData()) 80self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,} 81self.set=1 82raiseNodeProcessed# a MUST. Otherwise you'll get back an <iq type='error'/>
109""" Return specific jid's resource representation in internal format. Used internally. """110ifjid.find('/')+1:111jid,resource=jid.split('/',1)112ifself._data[jid]['resources'].has_key(resource):returnself._data[jid]['resources'][resource][dataname]113elifself._data[jid]['resources'].keys():114lastpri=-129115forrinself._data[jid]['resources'].keys():116ifint(self._data[jid]['resources'][r]['priority'])>lastpri:resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])117returnself._data[jid]['resources'][resource][dataname]
152""" Creates/renames contact 'jid' and sets the groups list that it now belongs to."""153iq=Iq('set',NS_ROSTER)154query=iq.getTag('query')155attrs={'jid':jid}156ifname:attrs['name']=name157item=query.setTag('item',attrs)158forgroupingroups:item.addChild(node=Node('group',payload=[group]))159self._owner.send(iq)
182""" Unauthorise JID 'jid'. Use for declining authorisation request 183 or for removing existing authorization. """184self._owner.send(Presence(jid,'unsubscribed'))
1## simplexml.py based on Mattew Allum's xmlstream.py 2## 3## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: simplexml.py,v 1.33 2007/09/11 12:46:16 normanr Exp $ 16 17"""Simplexml module provides xmpppy library with all needed tools to handle XML nodes and XML streams. 18I'm personally using it in many other separate projects. It is designed to be as standalone as possible.""" 19 20importxml.parsers.expat 21
23"""Returns provided string with symbols & < > " replaced by their respective XML entities.""" 24returntxt.replace("&","&").replace("<","<").replace(">",">").replace('"',""")
28"""Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise.""" 29iftype(what)==type(u''):returnwhat 30try:r=what.__str__() 31exceptAttributeError:r=str(what) 32iftype(r)<>type(u''):returnunicode(r,ENCODING) 33returnr
36""" Node class describes syntax of separate XML Node. It have a constructor that permits node creation 37 from set of "namespace name", attributes and payload of text strings and other nodes. 38 It does not natively support building node from text string and uses NodeBuilder class for that purpose. 39 After creation node can be mangled in many ways so it can be completely changed. 40 Also node can be serialised into string in one of two modes: default (where the textual representation 41 of node describes it exactly) and "fancy" - with whitespace added to make indentation and thus make 42 result more readable by human. 43 44 Node class have attribute FORCE_NODE_RECREATION that is defaults to False thus enabling fast node 45 replication from the some other node. The drawback of the fast way is that new node shares some 46 info with the "original" node that is changing the one node may influence the other. Though it is 47 rarely needed (in xmpppy it is never needed at all since I'm usually never using original node after 48 replication (and using replication only to move upwards on the classes tree). 49 """ 50FORCE_NODE_RECREATION=0
52""" Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it 53 by a space), attrs dictionary as the set of arguments, payload list as the set of textual strings 54 and child nodes that this node carries within itself and "parent" argument that is another node 55 that this one will be the child of. Also the __init__ can be provided with "node" argument that is 56 either a text string containing exactly one node or another Node instance to begin with. If both 57 "node" and other arguments is provided then the node initially created as replica of "node" 58 provided and then modified to be compliant with other arguments.""" 59ifnode: 60ifself.FORCE_NODE_RECREATIONandtype(node)==type(self):node=str(node) 61iftype(node)<>type(self):node=NodeBuilder(node,self) 62else: 63self.name,self.namespace,self.attrs,self.data,self.kids,self.parent=node.name,node.namespace,{},[],[],node.parent 64forkeyinnode.attrs.keys():self.attrs[key]=node.attrs[key] 65fordatainnode.data:self.data.append(data) 66forkidinnode.kids:self.kids.append(kid) 67else:self.name,self.namespace,self.attrs,self.data,self.kids,self.parent='tag','',{},[],[],None 68 69iftag:self.namespace,self.name=([self.namespace]+tag.split())[-2:] 70ifparent:self.parent=parent 71ifself.parentandnotself.namespace:self.namespace=self.parent.namespace 72forattrinattrs.keys(): 73self.attrs[attr]=attrs[attr] 74iftype(payload)in(type(''),type(u'')):payload=[payload] 75foriinpayload: 76iftype(i)==type(self):self.addChild(node=i) 77else:self.addData(i)
109""" Serialise node, dropping all tags and leaving CDATA intact.110 That is effectively kills all formatiing, leaving only text were contained in XML.111 """112s=""113cnt=0114ifself.kids:115forainself.kids:116s=s+self.data[cnt]117ifa:s=s+a.getCDATA()118cnt=cnt+1119if(len(self.data)-1)>=cnt:s=s+self.data[cnt]120returns
122""" If "node" argument is provided, adds it as child node. Else creates new node from123 the other arguments' values and adds it as well."""124ifattrs.has_key('xmlns'):125raiseAttributeError("Use namespace=x instead of attrs={'xmlns':x}")126ifnamespace:name=namespace+' '+name127ifnode:128newnode=node129node.parent=self130else:newnode=Node(tag=name,parent=self,attrs=attrs,payload=payload)131self.kids.append(newnode)132self.data.append(u'')133returnnewnode
145""" Deletes the "node" from the node's childs list, if "node" is an instance.146 Else deletes the first node that have specified name and (optionally) attributes. """147iftype(node)<>type(self):node=self.getTag(node,attrs)148self.kids[self.kids.index(node)]=None149returnnode
173""" Return the payload of node i.e. list of child nodes and CDATA entries.174 F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned list:175 ['text1', <nodea instance>, <nodeb instance>, ' text2']. """176ret=[]177foriinrange(max(len(self.data),len(self.kids))):178ifi<len(self.data)andself.data[i]:ret.append(self.data[i])179ifi<len(self.kids)andself.kids[i]:ret.append(self.kids[i])180returnret
182""" Filters all child nodes using specified arguments as filter.183 Returns the first found or None if not found. """184returnself.getTags(name,attrs,namespace,one=1)
186""" Returns attribute value of the child with specified name (or None if no such attribute)."""187try:returnself.getTag(tag).attrs[attr]188except:returnNone
194""" Filters all child nodes using specified arguments as filter.195 Returns the list of nodes found. """196nodes=[]197fornodeinself.kids:198ifnotnode:continue199ifnamespaceandnamespace<>node.getNamespace():continue200ifnode.getName()==name:201forkeyinattrs.keys():202ifnotnode.attrs.has_key(key)ornode.attrs[key]<>attrs[key]:break203else:nodes.append(node)204ifoneandnodes:returnnodes[0]205ifnotone:returnnodes
219""" Sets node's parent to "node". WARNING: do not checks if the parent already present 220 and not removes the node from the list of childs of previous parent. """221self.parent=node
223""" Sets node payload according to the list specified. WARNING: completely replaces all node's224 previous content. If you wish just to add child or CDATA - use addData or addChild methods. """225iftype(payload)in(type(''),type(u'')):payload=[payload]226ifadd:self.kids+=payload227else:self.kids=payload
229""" Same as getTag but if the node with specified namespace/attributes not found, creates such230 node and returns it. """231node=self.getTags(name,attrs,namespace=namespace,one=1)232ifnode:returnnode233else:returnself.addChild(name,attrs,namespace=namespace)
235""" Creates new node (if not already present) with name "tag"236 and sets it's attribute "attr" to value "val". """237try:self.getTag(tag).attrs[attr]=val238except:self.addChild(tag,attrs={attr:val})
240""" Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs"241 and sets it's CDATA to string "val". """242try:self.getTag(tag,attrs).setData(ustr(val))243except:self.addChild(tag,attrs,payload=[ustr(val)])
257""" Reduce memory usage caused by T/NT classes - use memory only when needed. """258ifattr=='T':259self.T=T(self)260returnself.T261ifattr=='NT':262self.NT=NT(self)263returnself.NT264raiseAttributeError
284""" Builds a Node class minidom from data parsed to it. This class used for two purposes:285 1. Creation an XML Node from a textual representation. F.e. reading a config file. See an XML2Node method.286 2. Handling an incoming XML stream. This is done by mangling 287 the __dispatch_depth parameter and redefining the dispatch method.288 You do not need to use this class directly if you do not designing your own XML handler."""
290""" Takes two optional parameters: "data" and "initial_node".291 By default class initialised with empty Node class instance.292 Though, if "initial_node" is provided it used as "starting point".293 You can think about it as of "node upgrade".294 "data" (if provided) feeded to parser immidiatedly after instance init.295 """296self.DEBUG(DBG_NODEBUILDER,"Preparing to handle incoming XML stream.",'start')297self._parser=xml.parsers.expat.ParserCreate(namespace_separator=' ')298self._parser.StartElementHandler=self.starttag299self._parser.EndElementHandler=self.endtag300self._parser.CharacterDataHandler=self.handle_data301self._parser.StartNamespaceDeclHandler=self.handle_namespace_start302self.Parse=self._parser.Parse303304self.__depth=0305self._dispatch_depth=1306self._document_attrs=None307self._mini_dom=initial_node308self.last_is_data=1309self._ptr=None310self.namespaces={"http://www.w3.org/XML/1998/namespace":'xml:'}311self.xmlns="http://www.w3.org/XML/1998/namespace"312313ifdata:self._parser.Parse(data,1)
316""" Method used to allow class instance to be garbage-collected. """317self._parser.StartElementHandler=None318self._parser.EndElementHandler=None319self._parser.CharacterDataHandler=None320self._parser.StartNamespaceDeclHandler=None
323"""XML Parser callback. Used internally"""324attlist=attrs.keys()#325forattrinattlist:# FIXME: Crude hack. And it also slows down the whole library considerably.326sp=attr.rfind(" ")#327ifsp==-1:continue#328ns=attr[:sp]#329attrs[self.namespaces[ns]+attr[sp+1:]]=attrs[attr]330delattrs[attr]#331self.__depth+=1332self.DEBUG(DBG_NODEBUILDER,"DEPTH -> %i , tag -> %s, attrs -> %s"%(self.__depth,tag,`attrs`),'down')333ifself.__depth==self._dispatch_depth:334ifnotself._mini_dom:self._mini_dom=Node(tag=tag,attrs=attrs)335else:Node.__init__(self._mini_dom,tag=tag,attrs=attrs)336self._ptr=self._mini_dom337elifself.__depth>self._dispatch_depth:338self._ptr.kids.append(Node(tag=tag,parent=self._ptr,attrs=attrs))339self._ptr=self._ptr.kids[-1]340ifself.__depth==1:341self._document_attrs=attrs342ns,name=(['']+tag.split())[-2:]343self.stream_header_received(ns,name,attrs)344ifnotself.last_is_dataandself._ptr.parent:self._ptr.parent.data.append('')345self.last_is_data=0
361"""XML Parser callback. Used internally"""362self.DEBUG(DBG_NODEBUILDER,data,'data')363ifnotself._ptr:return364ifself.last_is_data:365self._ptr.data[-1]+=data366else:367self._ptr.data.append(data)368self.last_is_data=1
380""" Gets called when the NodeBuilder reaches some level of depth on it's way up with the built381 node as argument. Can be redefined to convert incoming XML stanzas to program events. """
388""" Converts supplied textual string into XML node. Handy f.e. for reading configuration file.389 Raises xml.parser.expat.parsererror if provided string is not well-formed XML. """390returnNodeBuilder(xml).getDom()
393""" Converts supplied textual string into XML node. Survives if xml data is cutted half way round.394 I.e. "<html>some text <br>some more text". Will raise xml.parser.expat.parsererror on misplaced395 tags though. F.e. "<b>some text <br>some more text</b>" will not work."""396returnNodeBuilder(xml).getDom()
1## browser.py 2## 3## Copyright (C) 2004 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: browser.py,v 1.12 2007/05/13 17:55:14 normanr Exp $ 16 17"""Browser module provides DISCO server framework for your application. 18This functionality can be used for very different purposes - from publishing 19software version and supported features to building of "jabber site" that users 20can navigate with their disco browsers and interact with active content. 21 22Such functionality is achieved via registering "DISCO handlers" that are 23automatically called when user requests some node of your disco tree. 24""" 25 26fromdispatcherimport* 27fromclientimportPlugIn 28
30""" WARNING! This class is for components only. It will not work in client mode! 31 32 Standart xmpppy class that is ancestor of PlugIn and can be attached 33 to your application. 34 All processing will be performed in the handlers registered in the browser 35 instance. You can register any number of handlers ensuring that for each 36 node/jid combination only one (or none) handler registered. 37 You can register static information or the fully-blown function that will 38 calculate the answer dynamically. 39 Example of static info (see JEP-0030, examples 13-14): 40 # cl - your xmpppy connection instance. 41 b=xmpp.browser.Browser() 42 b.PlugIn(cl) 43 items=[] 44 item={} 45 item['jid']='catalog.shakespeare.lit' 46 item['node']='books' 47 item['name']='Books by and about Shakespeare' 48 items.append(item) 49 item={} 50 item['jid']='catalog.shakespeare.lit' 51 item['node']='clothing' 52 item['name']='Wear your literary taste with pride' 53 items.append(item) 54 item={} 55 item['jid']='catalog.shakespeare.lit' 56 item['node']='music' 57 item['name']='Music from the time of Shakespeare' 58 items.append(item) 59 info={'ids':[], 'features':[]} 60 b.setDiscoHandler({'items':items,'info':info}) 61 62 items should be a list of item elements. 63 every item element can have any of these four keys: 'jid', 'node', 'name', 'action' 64 info should be a dicionary and must have keys 'ids' and 'features'. 65 Both of them should be lists: 66 ids is a list of dictionaries and features is a list of text strings. 67 Example (see JEP-0030, examples 1-2) 68 # cl - your xmpppy connection instance. 69 b=xmpp.browser.Browser() 70 b.PlugIn(cl) 71 items=[] 72 ids=[] 73 ids.append({'category':'conference','type':'text','name':'Play-Specific Chatrooms'}) 74 ids.append({'category':'directory','type':'chatroom','name':'Play-Specific Chatrooms'}) 75 features=[NS_DISCO_INFO,NS_DISCO_ITEMS,NS_MUC,NS_REGISTER,NS_SEARCH,NS_TIME,NS_VERSION] 76 info={'ids':ids,'features':features} 77 # info['xdata']=xmpp.protocol.DataForm() # JEP-0128 78 b.setDiscoHandler({'items':[],'info':info}) 79 """
100""" Returns dictionary and key or None,None101 None - root node (w/o "node" attribute)102 /a/b/c - node103 /a/b/ - branch104 Set returns '' or None as the key105 get returns '' or None as the key or None as the dict.106 Used internally."""107ifself._handlers.has_key(jid):cur=self._handlers[jid]108elifset:109self._handlers[jid]={}110cur=self._handlers[jid]111else:cur=self._handlers['']112ifnodeisNone:node=[None]113else:node=node.replace('/',' /').split('/')114foriinnode:115ifi<>''andcur.has_key(i):cur=cur[i]116elifsetandi<>'':cur[i]={dict:cur,str:i};cur=cur[i]117elifsetorcur.has_key(''):returncur,''118else:returnNone,None119ifcur.has_key(1)orset:returncur,1120raise"Corrupted data"
123""" This is the main method that you will use in this class.124 It is used to register supplied DISCO handler (or dictionary with static info)125 as handler of some disco tree branch.126 If you do not specify the node this handler will be used for all queried nodes.127 If you do not specify the jid this handler will be used for all queried JIDs.128129 Usage:130 cl.Browser.setDiscoHandler(someDict,node,jid)131 or132 cl.Browser.setDiscoHandler(someDISCOHandler,node,jid)133 where134135 someDict={136 'items':[137 {'jid':'jid1','action':'action1','node':'node1','name':'name1'},138 {'jid':'jid2','action':'action2','node':'node2','name':'name2'},139 {'jid':'jid3','node':'node3','name':'name3'},140 {'jid':'jid4','node':'node4'}141 ],142 'info' :{143 'ids':[144 {'category':'category1','type':'type1','name':'name1'},145 {'category':'category2','type':'type2','name':'name2'},146 {'category':'category3','type':'type3','name':'name3'},147 ], 148 'features':['feature1','feature2','feature3','feature4'], 149 'xdata':DataForm150 }151 }152153 and/or154155 def someDISCOHandler(session,request,TYR):156 # if TYR=='items': # returns items list of the same format as shown above157 # elif TYR=='info': # returns info dictionary of the same format as shown above158 # else: # this case is impossible for now.159 """160self.DEBUG('Registering handler %s for "%s" node->%s'%(handler,jid,node),'info')161node,key=self._traversePath(node,jid,1)162node[key]=handler
165""" Returns the previously registered DISCO handler166 that is resonsible for this node/jid combination.167 Used internally."""168node,key=self._traversePath(node,jid)169ifnode:returnnode[key]
172""" Unregisters DISCO handler that is resonsible for this173 node/jid combination. When handler is unregistered the branch174 is handled in the same way that it's parent branch from this moment.175 """176node,key=self._traversePath(node,jid)177ifnode:178handler=node[key]179delnode[dict][node[str]]180returnhandler
183""" Servers DISCO iq request from the remote client.184 Automatically determines the best handler to use and calls it185 to handle the request. Used internally.186 """187node=request.getQuerynode()188ifnode:189nodestr=node190else:191nodestr='None'192handler=self.getDiscoHandler(node,request.getTo())193ifnothandler:194self.DEBUG("No Handler for request with jid->%s node->%s ns->%s"%(request.getTo().__str__().encode('utf8'),nodestr.encode('utf8'),request.getQueryNS().encode('utf8')),'error')195conn.send(Error(request,ERR_ITEM_NOT_FOUND))196raiseNodeProcessed197self.DEBUG("Handling request with jid->%s node->%s ns->%s"%(request.getTo().__str__().encode('utf8'),nodestr.encode('utf8'),request.getQueryNS().encode('utf8')),'ok')198rep=request.buildReply('result')199ifnode:rep.setQuerynode(node)200q=rep.getTag('query')201ifrequest.getQueryNS()==NS_DISCO_ITEMS:202# handler must return list: [{jid,action,node,name}]203iftype(handler)==dict:lst=handler['items']204else:lst=handler(conn,request,'items')205iflst==None:206conn.send(Error(request,ERR_ITEM_NOT_FOUND))207raiseNodeProcessed208foriteminlst:q.addChild('item',item)209elifrequest.getQueryNS()==NS_DISCO_INFO:210iftype(handler)==dict:dt=handler['info']211else:dt=handler(conn,request,'info')212ifdt==None:213conn.send(Error(request,ERR_ITEM_NOT_FOUND))214raiseNodeProcessed215# handler must return dictionary:216# {'ids':[{},{},{},{}], 'features':[fe,at,ur,es], 'xdata':DataForm}217foridindt['ids']:q.addChild('identity',id)218forfeatureindt['features']:q.addChild('feature',{'var':feature})219ifdt.has_key('xdata'):q.addChild(node=dt['xdata'])220conn.send(rep)221raiseNodeProcessed
60""" Attach to main instance and register ourself and all our staff in it. """ 61self._owner=owner 62ifself.DBG_LINEnotinowner.debug_flags: 63owner.debug_flags.append(self.DBG_LINE) 64self.DEBUG('Plugging %s into %s'%(self,self._owner),'start') 65ifowner.__dict__.has_key(self.__class__.__name__): 66returnself.DEBUG('Plugging ignored: another instance already plugged.','error') 67self._old_owners_methods=[] 68formethodinself._exported_methods: 69ifowner.__dict__.has_key(method.__name__): 70self._old_owners_methods.append(owner.__dict__[method.__name__]) 71owner.__dict__[method.__name__]=method 72owner.__dict__[self.__class__.__name__]=self 73ifself.__class__.__dict__.has_key('plugin'):returnself.plugin(owner)
76""" Unregister all our staff from main instance and detach from it. """ 77self.DEBUG('Plugging %s out of %s.'%(self,self._owner),'stop') 78ret=None 79ifself.__class__.__dict__.has_key('plugout'):ret=self.plugout() 80self._owner.debug_flags.remove(self.DBG_LINE) 81formethodinself._exported_methods:delself._owner.__dict__[method.__name__] 82formethodinself._old_owners_methods:self._owner.__dict__[method.__name__]=method 83delself._owner.__dict__[self.__class__.__name__] 84returnret
94""" Caches server name and (optionally) port to connect to. "debug" parameter specifies 95 the debug IDs that will go into debug output. You can either specifiy an "include" 96 or "exclude" list. The latter is done via adding "always" pseudo-ID to the list. 97 Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket', 98 'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] . """ 99ifself.__class__.__name__=='Client':self.Namespace,self.DBG='jabber:client',DBG_CLIENT100elifself.__class__.__name__=='Component':self.Namespace,self.DBG=dispatcher.NS_COMPONENT_ACCEPT,DBG_COMPONENT101self.defaultNamespace=self.Namespace102self.disconnect_handlers=[]103self.Server=server104self.Port=port105ifdebugandtype(debug)<>list:debug=['always','nodebuilder']106self._DEBUG=Debug.Debug(debug)107self.DEBUG=self._DEBUG.Show108self.debug_flags=self._DEBUG.debug_flags109self.debug_flags.append(self.DBG)110self._owner=self111self._registered_name=None112self.RegisterDisconnectHandler(self.DisconnectHandler)113self.connected=''114self._route=0
125""" Called on disconnection. Calls disconnect handlers and cleans things up. """126self.connected=''127self.DEBUG(self.DBG,'Disconnect detected','stop')128self.disconnect_handlers.reverse()129foriinself.disconnect_handlers:i()130self.disconnect_handlers.reverse()131ifself.__dict__.has_key('TLS'):self.TLS.PlugOut()
134""" Default disconnect handler. Just raises an IOError.135 If you choosed to use this class in your production client,136 override this method or at least unregister it. """137raiseIOError('Disconnected from server.')
148""" Example of reconnection method. In fact, it can be used to batch connection and auth as well. """149handlerssave=self.Dispatcher.dumpHandlers()150ifself.__dict__.has_key('ComponentBind'):self.ComponentBind.PlugOut()151ifself.__dict__.has_key('Bind'):self.Bind.PlugOut()152self._route=0153ifself.__dict__.has_key('NonSASL'):self.NonSASL.PlugOut()154ifself.__dict__.has_key('SASL'):self.SASL.PlugOut()155ifself.__dict__.has_key('TLS'):self.TLS.PlugOut()156self.Dispatcher.PlugOut()157ifself.__dict__.has_key('HTTPPROXYsocket'):self.HTTPPROXYsocket.PlugOut()158ifself.__dict__.has_key('TCPsocket'):self.TCPsocket.PlugOut()159ifnotself.connect(server=self._Server,proxy=self._Proxy):return160ifnotself.auth(self._User,self._Password,self._Resource):return161self.Dispatcher.restoreHandlers(handlerssave)162returnself.connected
165""" Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream.166 Returns None or 'tcp' or 'tls', depending on the result."""167ifnotserver:server=(self.Server,self.Port)168ifproxy:sock=transports.HTTPPROXYsocket(proxy,server,use_srv)169else:sock=transports.TCPsocket(server,use_srv)170connected=sock.PlugIn(self)171ifnotconnected:172sock.PlugOut()173return174self._Server,self._Proxy=server,proxy175self.connected='tcp'176if(sslisNoneandself.Connection.getPort()in(5223,443))orssl:177try:# FIXME. This should be done in transports.py178transports.TLS().PlugIn(self,now=1)179self.connected='ssl'180exceptsocket.sslerror:181return182dispatcher.Dispatcher().PlugIn(self)183whileself.Dispatcher.Stream._document_attrsisNone:184ifnotself.Process(1):return185ifself.Dispatcher.Stream._document_attrs.has_key('version')andself.Dispatcher.Stream._document_attrs['version']=='1.0':186whilenotself.Dispatcher.Stream.featuresandself.Process(1):pass# If we get version 1.0 stream the features tag MUST BE presented187returnself.connected
192""" Connect to jabber server. If you want to specify different ip/port to connect to you can193 pass it as tuple as first parameter. If there is HTTP proxy between you and server 194 specify it's address and credentials (if needed) in the second argument.195 If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)196 If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.197 If you want to disable tls/ssl support completely, set it to 0.198 Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})199 Returns '' or 'tcp' or 'tls', depending on the result."""200ifnotCommonClient.connect(self,server,proxy,secure,use_srv)orsecure<>Noneandnotsecure:returnself.connected201transports.TLS().PlugIn(self)202ifnotself.Dispatcher.Stream._document_attrs.has_key('version')ornotself.Dispatcher.Stream._document_attrs['version']=='1.0':returnself.connected203whilenotself.Dispatcher.Stream.featuresandself.Process(1):pass# If we get version 1.0 stream the features tag MUST BE presented204ifnotself.Dispatcher.Stream.features.getTag('starttls'):returnself.connected# TLS not supported by server205whilenotself.TLS.starttlsandself.Process(1):pass206ifnothasattr(self,'TLS')orself.TLS.starttls!='success':self.event('tls_failed');returnself.connected207self.connected='tls'208returnself.connected
211""" Authenticate connnection and bind resource. If resource is not provided212 random one or library name used. """213self._User,self._Password,self._Resource=user,password,resource214whilenotself.Dispatcher.Stream._document_attrsandself.Process(1):pass215ifself.Dispatcher.Stream._document_attrs.has_key('version')andself.Dispatcher.Stream._document_attrs['version']=='1.0':216whilenotself.Dispatcher.Stream.featuresandself.Process(1):pass# If we get version 1.0 stream the features tag MUST BE presented217ifsasl:auth.SASL(user,password).PlugIn(self)218ifnotsaslorself.SASL.startsasl=='not-supported':219ifnotresource:resource='xmpppy'220ifauth.NonSASL(user,password,resource).PlugIn(self):221self.connected+='+old_auth'222return'old_auth'223return224self.SASL.auth()225whileself.SASL.startsasl=='in-process'andself.Process(1):pass226ifself.SASL.startsasl=='success':227auth.Bind().PlugIn(self)228whileself.Bind.boundisNoneandself.Process(1):pass229ifself.Bind.Bind(resource):230self.connected+='+sasl'231return'sasl'232else:233ifself.__dict__.has_key('SASL'):self.SASL.PlugOut()
236""" Return the Roster instance, previously plugging it in and237 requesting roster from server if needed. """238ifnotself.__dict__.has_key('Roster'):roster.Roster().PlugIn(self)239returnself.Roster.getRoster()
242""" Send roster request and initial <presence/>.243 You can disable the first by setting requestRoster argument to 0. """244self.sendPresence(requestRoster=requestRoster)
247""" Send some specific presence state.248 Can also request roster from server if according agrument is set."""249ifrequestRoster:roster.Roster().PlugIn(self)250self.send(dispatcher.Presence(to=jid,typ=typ))
255""" Init function for Components.256 As components use a different auth mechanism which includes the namespace of the component.257 Jabberd1.4 and Ejabberd use the default namespace then for all client messages.258 Jabberd2 uses jabber:client.259 'server' argument is a server name that you are connecting to (f.e. "localhost").260 'port' can be specified if 'server' resolves to correct IP. If it is not then you'll need to specify IP 261 and port while calling "connect()"."""262CommonClient.__init__(self,server,port=port,debug=debug)263self.typ=typ264self.sasl=sasl265self.bind=bind266self.route=route267self.xcp=xcp268ifdomains:269self.domains=domains270else:271self.domains=[server]
302""" Authenticate component "name" with password "password"."""303self._User,self._Password,self._Resource=name,password,''304try:305ifself.sasl:auth.SASL(name,password).PlugIn(self)306ifnotself.saslorself.SASL.startsasl=='not-supported':307ifauth.NonSASL(name,password,'').PlugIn(self):308self.dobind(sasl=False)309self.connected+='+old_auth'310return'old_auth'311return312self.SASL.auth()313whileself.SASL.startsasl=='in-process'andself.Process(1):pass314ifself.SASL.startsasl=='success':315self.dobind(sasl=True)316self.connected+='+sasl'317return'sasl'318else:319raiseauth.NotAuthorized(self.SASL.startsasl)320except:321self.DEBUG(self.DBG,"Failed to authenticate %s"%name,'error')
1## filetransfer.py 2## 3## Copyright (C) 2004 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: filetransfer.py,v 1.6 2004/12/25 20:06:59 snakeru Exp $ 16 17""" 18This module contains IBB class that is the simple implementation of JEP-0047. 19Note that this is just a transport for data. You have to negotiate data transfer before 20(via StreamInitiation most probably). Unfortunately SI is not implemented yet. 21""" 22 23fromprotocolimport* 24fromdispatcherimportPlugIn 25importbase64 26
28""" IBB used to transfer small-sized data chunk over estabilished xmpp connection. 29 Data is split into small blocks (by default 3000 bytes each), encoded as base 64 30 and sent to another entity that compiles these blocks back into the data chunk. 31 This is very inefficiend but should work under any circumstances. Note that 32 using IBB normally should be the last resort. 33 """
86""" Start new stream. You should provide stream id 'sid', the endpoind jid 'to', 87 the file object containing info for send 'fp'. Also the desired blocksize can be specified. 88 Take into account that recommended stanza size is 4k and IBB uses base64 encoding 89 that increases size of data by 1/3.""" 90ifsidinself._streams.keys():return 91ifnotJID(to).getResource():return 92self._streams[sid]={'direction':'|>'+to,'block-size':blocksize,'fp':fp,'seq':0} 93self._owner.RegisterCycleHandler(self.SendHandler) 94syn=Protocol('iq',to,'set',payload=[Node(NS_IBB+' open',{'sid':sid,'block-size':blocksize})]) 95self._owner.send(syn) 96self._streams[sid]['syn_id']=syn.getID() 97returnself._streams[sid]
100""" Send next portion of data if it is time to do it. Used internally. """101self.DEBUG('SendHandler called','info')102forsidinself._streams.keys():103stream=self._streams[sid]104ifstream['direction'][:2]=='|>':cont=1105elifstream['direction'][0]=='>':106chunk=stream['fp'].read(stream['block-size'])107ifchunk:108datanode=Node(NS_IBB+' data',{'sid':sid,'seq':stream['seq']},base64.encodestring(chunk))109stream['seq']+=1110ifstream['seq']==65536:stream['seq']=0111conn.send(Protocol('message',stream['direction'][1:],payload=[datanode,self._ampnode]))112else:113""" notify the other side about stream closing114 notify the local user about sucessfull send115 delete the local stream"""116conn.send(Protocol('iq',stream['direction'][1:],'set',payload=[Node(NS_IBB+' close',{'sid':sid})]))117conn.Event(self.DBG_LINE,'SUCCESSFULL SEND',stream)118delself._streams[sid]119self._owner.UnregisterCycleHandler(self.SendHandler)120121"""122<message from='romeo@montague.net/orchard' to='juliet@capulet.com/balcony' id='msg1'>123 <data xmlns='http://jabber.org/protocol/ibb' sid='mySID' seq='0'>124 qANQR1DBwU4DX7jmYZnncmUQB/9KuKBddzQH+tZ1ZywKK0yHKnq57kWq+RFtQdCJ125 WpdWpR0uQsuJe7+vh3NWn59/gTc5MDlX8dS9p0ovStmNcyLhxVgmqS8ZKhsblVeu126 IpQ0JgavABqibJolc3BKrVtVV1igKiX/N7Pi8RtY1K18toaMDhdEfhBRzO/XB0+P127 AQhYlRjNacGcslkhXqNjK5Va4tuOAPy2n1Q8UUrHbUd0g+xJ9Bm0G0LZXyvCWyKH128 kuNEHFQiLuCY6Iv0myq6iX6tjuHehZlFSh80b5BVV9tNLwNR5Eqz1klxMhoghJOA129 </data>130 <amp xmlns='http://jabber.org/protocol/amp'>131 <rule condition='deliver-at' value='stored' action='error'/>132 <rule condition='match-resource' value='exact' action='error'/>133 </amp>134</message>135"""
138""" Receive next portion of incoming datastream and store it write139 it to temporary file. Used internally.140 """141sid,seq,data=stanza.getTagAttr('data','sid'),stanza.getTagAttr('data','seq'),stanza.getTagData('data')142self.DEBUG('ReceiveHandler called sid->%s seq->%s'%(sid,seq),'info')143try:seq=int(seq);data=base64.decodestring(data)144except:seq='';data=''145err=None146ifnotsidinself._streams.keys():err=ERR_ITEM_NOT_FOUND147else:148stream=self._streams[sid]149ifnotdata:err=ERR_BAD_REQUEST150elifseq<>stream['seq']:err=ERR_UNEXPECTED_REQUEST151else:152self.DEBUG('Successfull receive sid->%s %s+%s bytes'%(sid,stream['fp'].tell(),len(data)),'ok')153stream['seq']+=1154stream['fp'].write(data)155iferr:156self.DEBUG('Error on receive: %s'%err,'error')157conn.send(Error(Iq(to=stanza.getFrom(),frm=stanza.getTo(),payload=[Node(NS_IBB+' close')]),err,reply=0))
160""" Handle stream closure due to all data transmitted.161 Raise xmpppy event specifying successfull data receive. """162sid=stanza.getTagAttr('close','sid')163self.DEBUG('StreamCloseHandler called sid->%s'%sid,'info')164ifsidinself._streams.keys():165conn.send(stanza.buildReply('result'))166conn.Event(self.DBG_LINE,'SUCCESSFULL RECEIVE',self._streams[sid])167delself._streams[sid]168else:conn.send(Error(stanza,ERR_ITEM_NOT_FOUND))
171""" Handle stream closure due to all some error while receiving data.172 Raise xmpppy event specifying unsuccessfull data receive. """173syn_id=stanza.getID()174self.DEBUG('StreamBrokenHandler called syn_id->%s'%syn_id,'info')175forsidinself._streams.keys():176stream=self._streams[sid]177ifstream['syn_id']==syn_id:178ifstream['direction'][0]=='<':conn.Event(self.DBG_LINE,'ERROR ON RECEIVE',stream)179else:conn.Event(self.DBG_LINE,'ERROR ON SEND',stream)180delself._streams[sid]
183""" Handle remote side reply about is it agree or not to receive our datastream.184 Used internally. Raises xmpppy event specfiying if the data transfer185 is agreed upon."""186syn_id=stanza.getID()187self.DEBUG('StreamOpenReplyHandler called syn_id->%s'%syn_id,'info')188forsidinself._streams.keys():189stream=self._streams[sid]190ifstream['syn_id']==syn_id:191ifstanza.getType()=='error':192ifstream['direction'][0]=='<':conn.Event(self.DBG_LINE,'ERROR ON RECEIVE',stream)193else:conn.Event(self.DBG_LINE,'ERROR ON SEND',stream)194delself._streams[sid]195elifstanza.getType()=='result':196ifstream['direction'][0]=='|':197stream['direction']=stream['direction'][1:]198conn.Event(self.DBG_LINE,'STREAM COMMITTED',stream)199else:conn.send(Error(stanza,ERR_UNEXPECTED_REQUEST))
1## debug.py 2## 3## Copyright (C) 2003 Jacob Lundqvist 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU Lesser General Public License as published 7## by the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU Lesser General Public License for more details. 14 15_version_='1.4.0' 16 17"""\ 18 19Generic debug class 20 21Other modules can always define extra debug flags for local usage, as long as 22they make sure they append them to debug_flags 23 24Also its always a good thing to prefix local flags with something, to reduce risk 25of coliding flags. Nothing breaks if two flags would be identical, but it might 26activate unintended debugging. 27 28flags can be numeric, but that makes analysing harder, on creation its 29not obvious what is activated, and when flag_show is given, output isnt 30really meaningfull. 31 32This Debug class can either be initialized and used on app level, or used independantly 33by the individual classes. 34 35For samples of usage, see samples subdir in distro source, and selftest 36in this code 37 38""" 39 40 41 42importsys 43importtraceback 44importtime 45importos 46 47importtypes 48 49ifos.environ.has_key('TERM'): 50colors_enabled=True 51else: 52colors_enabled=False 53 54color_none=chr(27)+"[0m" 55color_black=chr(27)+"[30m" 56color_red=chr(27)+"[31m" 57color_green=chr(27)+"[32m" 58color_brown=chr(27)+"[33m" 59color_blue=chr(27)+"[34m" 60color_magenta=chr(27)+"[35m" 61color_cyan=chr(27)+"[36m" 62color_light_gray=chr(27)+"[37m" 63color_dark_gray=chr(27)+"[30;1m" 64color_bright_red=chr(27)+"[31;1m" 65color_bright_green=chr(27)+"[32;1m" 66color_yellow=chr(27)+"[33;1m" 67color_bright_blue=chr(27)+"[34;1m" 68color_purple=chr(27)+"[35;1m" 69color_bright_cyan=chr(27)+"[36;1m" 70color_white=chr(27)+"[37;1m" 71 72 73""" 74Define your flags in yor modules like this: 75 76from debug import * 77 78DBG_INIT = 'init' ; debug_flags.append( DBG_INIT ) 79DBG_CONNECTION = 'connection' ; debug_flags.append( DBG_CONNECTION ) 80 81 The reason for having a double statement wis so we can validate params 82 and catch all undefined debug flags 83 84 This gives us control over all used flags, and makes it easier to allow 85 global debugging in your code, just do something like 86 87 foo = Debug( debug_flags ) 88 89 group flags, that is a flag in it self containing multiple flags should be 90 defined without the debug_flags.append() sequence, since the parts are already 91 in the list, also they must of course be defined after the flags they depend on ;) 92 example: 93 94DBG_MULTI = [ DBG_INIT, DBG_CONNECTION ] 95 96 97 98 NoDebug 99 -------100 To speed code up, typically for product releases or such101 use this class instead if you globaly want to disable debugging102"""103104
123-def__init__(self,124#125# active_flags are those that will trigger output126#127active_flags=None,128#129# Log file should be file object or file namne130#131log_file=sys.stderr,132#133# prefix and sufix can either be set globaly or per call.134# personally I use this to color code debug statements135# with prefix = chr(27) + '[34m'136# sufix = chr(27) + '[37;1m\n'137#138prefix='DEBUG: ',139sufix='\n',140#141# If you want unix style timestamps, 142# 0 disables timestamps143# 1 before prefix, good when prefix is a string144# 2 after prefix, good when prefix is a color145#146time_stamp=0,147#148# flag_show should normaly be of, but can be turned on to get a149# good view of what flags are actually used for calls,150# if it is not None, it should be a string151# flags for current call will be displayed 152# with flag_show as separator 153# recomended values vould be '-' or ':', but any string goes154#155flag_show=None,156#157# If you dont want to validate flags on each call to158# show(), set this to 0159#160validate_flags=1,161#162# If you dont want the welcome message, set to 0163# default is to show welcome if any flags are active164welcome=-1165):
166167self.debug_flags=[]168ifwelcome==-1:169ifactive_flagsandlen(active_flags):170welcome=1171else:172welcome=0173174self._remove_dupe_flags()175iflog_file:176iftype(log_file)istype(''):177try:178self._fh=open(log_file,'w')179except:180print'ERROR: can open %s for writing'181sys.exit(0)182else:## assume its a stream type object183self._fh=log_file184else:185self._fh=sys.stdout186187iftime_stampnotin(0,1,2):188msg2='%s'%time_stamp189raise'Invalid time_stamp param',msg2190self.prefix=prefix191self.sufix=sufix192self.time_stamp=time_stamp193self.flag_show=None# must be initialised after possible welcome194self.validate_flags=validate_flags195196self.active_set(active_flags)197ifwelcome:198self.show('')199caller=sys._getframe(1)# used to get name of caller200try:201mod_name=":%s"%caller.f_locals['__name__']202except:203mod_name=""204self.show('Debug created for %s%s'%(caller.f_code.co_filename,205mod_name))206self.show(' flags defined: %s'%','.join(self.active))207208iftype(flag_show)in(type(''),type(None)):209self.flag_show=flag_show210else:211msg2='%s'%type(flag_show)212raise'Invalid type for flag_show!',msg2
220"""221 flag can be of folowing types:222 None - this msg will always be shown if any debugging is on223 flag - will be shown if flag is active224 (flag1,flag2,,,) - will be shown if any of the given flags 225 are active226227 if prefix / sufix are not given, default ones from init will be used228229 lf = -1 means strip linefeed if pressent230 lf = 1 means add linefeed if not pressent231 """232233ifself.validate_flags:234self._validate_flag(flag)235236ifnotself.is_active(flag):237return238ifprefix:239pre=prefix240else:241pre=self.prefix242ifsufix:243suf=sufix244else:245suf=self.sufix246247ifself.time_stamp==2:248output='%s%s '%(pre,249time.strftime('%b %d %H:%M:%S',250time.localtime(time.time())),251)252elifself.time_stamp==1:253output='%s %s'%(time.strftime('%b %d %H:%M:%S',254time.localtime(time.time())),255pre,256)257else:258output=pre259260ifself.flag_show:261ifflag:262output='%s%s%s'%(output,flag,self.flag_show)263else:264# this call uses the global default,265# dont print "None", just show the separator266output='%s %s'%(output,self.flag_show)267268output='%s%s%s'%(output,msg,suf)269iflf:270# strip/add lf if needed271last_char=output[-1]272iflf==1andlast_char!=LINE_FEED:273output=output+LINE_FEED274eliflf==-1andlast_char==LINE_FEED:275output=output[:-1]276try:277self._fh.write(output)278except:279# unicode strikes again ;)280s=u''281foriinrange(len(output)):282iford(output[i])<128:283c=output[i]284else:285c='?'286s=s+c287self._fh.write('%s%s%s'%(pre,s,suf))288self._fh.flush()
292'If given flag(s) should generate output.'293294# try to abort early to quicken code295ifnotself.active:296return0297ifnotflagorflaginself.active:298return1299else:300# check for multi flag type:301iftype(flag)in(type(()),type([])):302forsinflag:303ifsinself.active:304return1305return0
309"returns 1 if any flags where actually set, otherwise 0."310r=0311ok_flags=[]312ifnotactive_flags:313#no debuging at all314self.active=[]315eliftype(active_flags)in(types.TupleType,types.ListType):316flags=self._as_one_list(active_flags)317fortinflags:318iftnotinself.debug_flags:319sys.stderr.write('Invalid debugflag given: %s\n'%t)320ok_flags.append(t)321322self.active=ok_flags323r=1324else:325# assume comma string326try:327flags=active_flags.split(',')328except:329self.show('***')330self.show('*** Invalid debug param given: %s'%active_flags)331self.show('*** please correct your param!')332self.show('*** due to this, full debuging is enabled')333self.active=self.debug_flags334335forfinflags:336s=f.strip()337ok_flags.append(s)338self.active=ok_flags339340self._remove_dupe_flags()341returnr
349""" init param might contain nested lists, typically from group flags.350351 This code organises lst and remves dupes352 """353iftype(items)<>type([])andtype(items)<>type(()):354return[items]355r=[]356forlinitems:357iftype(l)==type([]):358lst2=self._as_one_list(l)359forl2inlst2:360self._append_unique_str(r,l2)361elifl==None:362continue363else:364self._append_unique_str(r,l)365returnr
369"""filter out any dupes."""370iftype(item)<>type(''):371msg2='%s'%item372raise'Invalid item type (should be string)',msg2373ifitemnotinlst:374lst.append(item)375returnlst
379'verify that flag is defined.'380ifflags:381forfinself._as_one_list(flags):382ifnotfinself.debug_flags:383msg2='%s'%f384raise'Invalid debugflag given',msg2
387"""388 if multiple instances of Debug is used in same app, 389 some flags might be created multiple time, filter out dupes390 """391unique_flags=[]392forfinself.debug_flags:393iffnotinunique_flags:394unique_flags.append(f)395self.debug_flags=unique_flags
1 2# JID Escaping XEP-0106 for the xmpppy based transports written by Norman Rasmussen 3 4"""This file is the XEP-0106 commands. 5 6Implemented commands as follows: 7 84.2 Encode : Encoding Transformation 94.3 Decode : Decoding Transformation101112"""1314xep0106mapping=[15[' ','20'],16['"','22'],17['&','26'],18['\'','27'],19['/','2f'],20[':','3a'],21['<','3c'],22['>','3e'],23['@','40']]24
1## transports.py 2## 3## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: dispatcher.py,v 1.42 2007/05/18 23:18:36 normanr Exp $ 16 17""" 18Main xmpppy mechanism. Provides library with methods to assign different handlers 19to different XMPP stanzas. 20Contains one tunable attribute: DefaultTimeout (25 seconds by default). It defines time that 21Dispatcher.SendAndWaitForResponce method will wait for reply stanza before giving up. 22""" 23 24importsimplexml,time,sys 25fromprotocolimport* 26fromclientimportPlugIn 27 28DefaultTimeout=25 29ID=0 30
32""" Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers. 33 Can be plugged out/in to restart these headers (used for SASL f.e.). """
50""" Return set of user-registered callbacks in it's internal format. 51 Used within the library to carry user handlers set over Dispatcher replugins. """ 52returnself.handlers
54""" Restores user-registered callbacks structure from dump previously obtained via dumpHandlers. 55 Used within the library to carry user handlers set over Dispatcher replugins. """ 56self.handlers=handlers
70""" Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally.""" 71self._init() 72formethodinself._old_owners_methods: 73ifmethod.__name__=='send':self._owner_send=method;break 74self._owner.lastErrNode=None 75self._owner.lastErr=None 76self._owner.lastErrCode=None 77self.StreamInit()
107""" Check incoming stream for data waiting. If "timeout" is positive - block for as max. this time.108 Returns:109 1) length of processed data if some data were processed;110 2) '0' string if no data were processed but link is alive;111 3) 0 (zero) if underlying connection is closed.112 Take note that in case of disconnection detect during Process() call113 disconnect handlers are called automatically.114 """115forhandlerinself._cycleHandlers:handler(self)116iflen(self._pendingExceptions)>0:117_pendingException=self._pendingExceptions.pop()118raise_pendingException[0],_pendingException[1],_pendingException[2]119ifself._owner.Connection.pending_data(timeout):120try:data=self._owner.Connection.receive()121exceptIOError:return122self.Stream.Parse(data)123iflen(self._pendingExceptions)>0:124_pendingException=self._pendingExceptions.pop()125raise_pendingException[0],_pendingException[1],_pendingException[2]126ifdata:returnlen(data)127return'0'# It means that nothing is received but link is alive.
130""" Creates internal structures for newly registered namespace.131 You can register handlers for this namespace afterwards. By default one namespace132 already registered (jabber:client or jabber:component:accept depending on context. """133self.DEBUG('Registering namespace "%s"'%xmlns,order)134self.handlers[xmlns]={}135self.RegisterProtocol('unknown',Protocol,xmlns=xmlns)136self.RegisterProtocol('default',Protocol,xmlns=xmlns)
139""" Used to declare some top-level stanza name to dispatcher.140 Needed to start registering handlers for such stanzas.141 Iq, message and presence protocols are registered by default. """142ifnotxmlns:xmlns=self._owner.defaultNamespace143self.DEBUG('Registering protocol "%s" as %s(%s)'%(tag_name,Proto,xmlns),order)144self.handlers[xmlns][tag_name]={type:Proto,'default':[]}
147""" Register handler for processing all stanzas for specified namespace. """148self.RegisterHandler('default',handler,typ,ns,xmlns,makefirst,system)
151"""Register user callback as stanzas handler of declared type. Callback must take152 (if chained, see later) arguments: dispatcher instance (for replying), incomed153 return of previous handlers.154 The callback must raise xmpp.NodeProcessed just before return if it want preven155 callbacks to be called with the same stanza as argument _and_, more importantly156 library from returning stanza to sender with error set (to be enabled in 0.2 ve157 Arguments:158 "name" - name of stanza. F.e. "iq".159 "handler" - user callback.160 "typ" - value of stanza's "type" attribute. If not specified any value match161 "ns" - namespace of child that stanza must contain.162 "chained" - chain together output of several handlers.163 "makefirst" - insert handler in the beginning of handlers list instead of164 adding it to the end. Note that more common handlers (i.e. w/o "typ" and "165 will be called first nevertheless.166 "system" - call handler even if NodeProcessed Exception were raised already.167 """168ifnotxmlns:xmlns=self._owner.defaultNamespace169self.DEBUG('Registering handler %s for "%s" type->%s ns->%s(%s)'%(handler,name,typ,ns,xmlns),'info')170ifnottypandnotns:typ='default'171ifnotself.handlers.has_key(xmlns):self.RegisterNamespace(xmlns,'warn')172ifnotself.handlers[xmlns].has_key(name):self.RegisterProtocol(name,Protocol,xmlns,'warn')173ifnotself.handlers[xmlns][name].has_key(typ+ns):self.handlers[xmlns][name][typ+ns]=[]174ifmakefirst:self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler,'system':system})175else:self.handlers[xmlns][name][typ+ns].append({'func':handler,'system':system})
178""" Unregister handler after first call (not implemented yet). """179ifnotxmlns:xmlns=self._owner.defaultNamespace180self.RegisterHandler(name,handler,typ,ns,xmlns,makefirst,system)
183""" Unregister handler. "typ" and "ns" must be specified exactly the same as with registering."""184ifnotxmlns:xmlns=self._owner.defaultNamespace185ifnotself.handlers.has_key(xmlns):return186ifnottypandnotns:typ='default'187forpackinself.handlers[xmlns][name][typ+ns]:188ifhandler==pack['func']:break189else:pack=None190try:self.handlers[xmlns][name][typ+ns].remove(pack)191exceptValueError:pass
194""" Specify the handler that will be used if no NodeProcessed exception were raised.195 This is returnStanzaHandler by default. """196self._defaultHandler=handler
203""" Return stanza back to the sender with <feature-not-implemennted/> error set. """204ifstanza.getType()in['get','set']:205conn.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED))
218""" Register handler that will be called on every Dispatcher.Process() call. """219ifhandlernotinself._cycleHandlers:self._cycleHandlers.append(handler)
222""" Unregister handler that will is called on every Dispatcher.Process() call."""223ifhandlerinself._cycleHandlers:self._cycleHandlers.remove(handler)
226""" Raise some event. Takes three arguments:227 1) "realm" - scope of event. Usually a namespace. 228 2) "event" - the event itself. F.e. "SUCESSFULL SEND".229 3) data that comes along with event. Depends on event."""230ifself._eventHandler:self._eventHandler(realm,event,data)
233""" Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.234 Called internally. """235ifnotsession:session=self236session.Stream._mini_dom=None237name=stanza.getName()238239ifnotdirectandself._owner._route:240ifname=='route':241ifstanza.getAttr('error')==None:242iflen(stanza.getChildren())==1:243stanza=stanza.getChildren()[0]244name=stanza.getName()245else:246foreachinstanza.getChildren():247self.dispatch(each,session,direct=1)248return249elifname=='presence':250return251elifnamein('features','bind'):252pass253else:254raiseUnsupportedStanzaType(name)255256ifname=='features':session.Stream.features=stanza257258xmlns=stanza.getNamespace()259ifnotself.handlers.has_key(xmlns):260self.DEBUG("Unknown namespace: "+xmlns,'warn')261xmlns='unknown'262ifnotself.handlers[xmlns].has_key(name):263self.DEBUG("Unknown stanza: "+name,'warn')264name='unknown'265else:266self.DEBUG("Got %s/%s stanza"%(xmlns,name),'ok')267268ifstanza.__class__.__name__=='Node':stanza=self.handlers[xmlns][name][type](node=stanza)269270typ=stanza.getType()271ifnottyp:typ=''272stanza.props=stanza.getProperties()273ID=stanza.getID()274275session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s"%(name,typ,stanza.props,ID),'ok')276277list=['default']# we will use all handlers:278ifself.handlers[xmlns][name].has_key(typ):list.append(typ)# from very common...279forpropinstanza.props:280ifself.handlers[xmlns][name].has_key(prop):list.append(prop)281iftypandself.handlers[xmlns][name].has_key(typ+prop):list.append(typ+prop)# ...to very particular282283chain=self.handlers[xmlns]['default']['default']284forkeyinlist:285ifkey:chain=chain+self.handlers[xmlns][name][key]286287output=''288ifsession._expected.has_key(ID):289user=0290iftype(session._expected[ID])==type(()):291cb,args=session._expected[ID]292session.DEBUG("Expected stanza arrived. Callback %s(%s) found!"%(cb,args),'ok')293try:cb(session,stanza,**args)294exceptException,typ:295iftyp.__class__.__name__<>'NodeProcessed':raise296else:297session.DEBUG("Expected stanza arrived!",'ok')298session._expected[ID]=stanza299else:user=1300forhandlerinchain:301ifuserorhandler['system']:302try:303handler['func'](session,stanza)304exceptException,typ:305iftyp.__class__.__name__<>'NodeProcessed':306self._pendingExceptions.insert(0,sys.exc_info())307return308user=0309ifuserandself._defaultHandler:self._defaultHandler(session,stanza)
312""" Block and wait until stanza with specific "id" attribute will come.313 If no such stanza is arrived within timeout, return None.314 If operation failed for some reason then owner's attributes315 lastErrNode, lastErr and lastErrCode are set accordingly. """316self._expected[ID]=None317has_timed_out=0318abort_time=time.time()+timeout319self.DEBUG("Waiting for ID:%s with timeout %s..."%(ID,timeout),'wait')320whilenotself._expected[ID]:321ifnotself.Process(0.04):322self._owner.lastErr="Disconnect"323returnNone324iftime.time()>abort_time:325self._owner.lastErr="Timeout"326returnNone327response=self._expected[ID]328delself._expected[ID]329ifresponse.getErrorCode():330self._owner.lastErrNode=response331self._owner.lastErr=response.getError()332self._owner.lastErrCode=response.getErrorCode()333returnresponse
340""" Put stanza on the wire and call back when recipient replies.341 Additional callback arguments can be specified in args. """342self._expected[self.send(stanza)]=(func,args)
345""" Serialise stanza and put it on the wire. Assign an unique ID to it before send.346 Returns assigned ID."""347iftype(stanza)in[type(''),type(u'')]:returnself._owner_send(stanza)348ifnotisinstance(stanza,Protocol):_ID=None349elifnotstanza.getID():350globalID351ID+=1352_ID=`ID`353stanza.setID(_ID)354else:_ID=stanza.getID()355ifself._owner._registered_nameandnotstanza.getAttr('from'):stanza.setAttr('from',self._owner._registered_name)356ifself._owner._routeandstanza.getName()!='bind':357to=self._owner.Server358ifstanza.getTo()andstanza.getTo().getDomain():359to=stanza.getTo().getDomain()360frm=stanza.getFrom()361iffrm.getDomain():362frm=frm.getDomain()363route=Protocol('route',to=to,frm=frm,payload=[stanza])364stanza=route365stanza.setNamespace(self._owner.Namespace)366stanza.setParent(self._metastream)367self._owner_send(stanza)368return_ID
371""" Send a stream terminator and and handle all incoming stanzas before stream closure. """372self._owner_send('</stream:stream>')373whileself.Process(1):pass
1## 2## XMPP server 3## 4## Copyright (C) 2004 Alexey "Snake" Nezhdanov 5## 6## This program is free software; you can redistribute it and/or modify 7## it under the terms of the GNU General Public License as published by 8## the Free Software Foundation; either version 2, or (at your option) 9## any later version. 10## 11## This program is distributed in the hope that it will be useful, 12## but WITHOUT ANY WARRANTY; without even the implied warranty of 13## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14## GNU General Public License for more details. 15 16__version__="$Id" 17 18""" 19When your handler is called it is getting the session instance as the first argument. 20This is the difference from xmpppy 0.1 where you got the "Client" instance. 21With Session class you can have "multi-session" client instead of having 22one client for each connection. Is is specifically important when you are 23writing the server. 24""" 25 26fromprotocolimport* 27 28# Transport-level flags 29SOCKET_UNCONNECTED=0 30SOCKET_ALIVE=1 31SOCKET_DEAD=2 32# XML-level flags 33STREAM__NOT_OPENED=1 34STREAM__OPENED=2 35STREAM__CLOSING=3 36STREAM__CLOSED=4 37# XMPP-session flags 38SESSION_NOT_AUTHED=1 39SESSION_AUTHED=2 40SESSION_BOUND=3 41SESSION_OPENED=4 42SESSION_CLOSED=5 43
45""" 46 The Session class instance is used for storing all session-related info like 47 credentials, socket/xml stream/session state flags, roster items (in case of 48 client type connection) etc. 49 Session object have no means of discovering is any info is ready to be read. 50 Instead you should use poll() (recomended) or select() methods for this purpose. 51 Session can be one of two types: 'server' and 'client'. 'server' session handles 52 inbound connection and 'client' one used to create an outbound one. 53 Session instance have multitude of internal attributes. The most imporant is the 'peer' one. 54 It is set once the peer is authenticated (client). 55 """
57""" When the session is created it's type (client/server) is determined from the beginning. 58 socket argument is the pre-created socket-like object. 59 It must have the following methods: send, recv, fileno, close. 60 owner is the 'master' instance that have Dispatcher plugged into it and generally 61 will take care about all session events. 62 xmlns is the stream namespace that will be used. Client must set this argument 63 If server sets this argument than stream will be dropped if opened with some another namespace. 64 peer is the name of peer instance. This is the flag that differentiates client session from 65 server session. Client must set it to the name of the server that will be connected, server must 66 leave this argument alone. 67 """ 68self.xmlns=xmlns 69ifpeer: 70self.TYP='client' 71self.peer=peer 72self._socket_state=SOCKET_UNCONNECTED 73else: 74self.TYP='server' 75self.peer=None 76self._socket_state=SOCKET_ALIVE 77self._sock=socket 78self._send=socket.send 79self._recv=socket.recv 80self.fileno=socket.fileno 81self._registered=0 82 83self.Dispatcher=owner.Dispatcher 84self.DBG_LINE='session' 85self.DEBUG=owner.Dispatcher.DEBUG 86self._expected={} 87self._owner=owner 88ifself.TYP=='server':self.ID=`random.random()`[2:] 89else:self.ID=None 90 91self.sendbuffer='' 92self._stream_pos_queued=None 93self._stream_pos_sent=0 94self.deliver_key_queue=[] 95self.deliver_queue_map={} 96self.stanza_queue=[] 97 98self._session_state=SESSION_NOT_AUTHED 99self.waiting_features=[]100forfeaturein[NS_TLS,NS_SASL,NS_BIND,NS_SESSION]:101iffeatureinowner.features:self.waiting_features.append(feature)102self.features=[]103self.feature_in_process=None104self.slave_session=None105self.StartStream()
124""" Reads all pending incoming data.125 Raises IOError on disconnection.126 Blocks until at least one byte is read."""127try:received=self._recv(10240)128except:received=''129130iflen(received):# length of 0 means disconnect131self.DEBUG(`self.fileno()`+' '+received,'got')132else:133self.DEBUG('Socket error while receiving data','error')134self.set_socket_state(SOCKET_DEAD)135raiseIOError("Peer disconnected")136returnreceived
139""" Put chunk into "immidiatedly send" queue.140 Should only be used for auth/TLS stuff and like.141 If you just want to shedule regular stanza for delivery use enqueue method.142 """143ifisinstance(chunk,Node):chunk=chunk.__str__().encode('utf-8')144eliftype(chunk)==type(u''):chunk=chunk.encode('utf-8')145self.enqueue(chunk)
148""" Takes Protocol instance as argument.149 Puts stanza into "send" fifo queue. Items into the send queue are hold until150 stream authenticated. After that this method is effectively the same as "sendnow" method."""151ifisinstance(stanza,Protocol):152self.stanza_queue.append(stanza)153else:self.sendbuffer+=stanza154ifself._socket_state>=SOCKET_ALIVE:self.push_queue()
157""" If stream is authenticated than move items from "send" queue to "immidiatedly send" queue.158 Else if the stream is failed then return all queued stanzas with error passed as argument.159 Otherwise do nothing."""160# If the stream authed - convert stanza_queue into sendbuffer and set the checkpoints161162ifself._stream_state>=STREAM__CLOSEDorself._socket_state>=SOCKET_DEAD:# the stream failed. Return all stanzas that are still waiting for delivery.163self._owner.deactivatesession(self)164forkeyinself.deliver_key_queue:# Not sure. May be I165self._dispatch(Error(self.deliver_queue_map[key],failreason),trusted=1)# should simply re-dispatch it?166forstanzainself.stanza_queue:# But such action can invoke167self._dispatch(Error(stanza,failreason),trusted=1)# Infinite loops in case of S2S connection...168self.deliver_queue_map,self.deliver_key_queue,self.stanza_queue={},[],[]169return170elifself._session_state>=SESSION_AUTHED:# FIXME! äÏÌÖÅÎ ÂÙÔØ ËÁËÏÊ-ÔÏ ÄÒÕÇÏÊ ÆÌÁÇ.171#### LOCK_QUEUE172forstanzainself.stanza_queue:173txt=stanza.__str__().encode('utf-8')174self.sendbuffer+=txt175self._stream_pos_queued+=len(txt)# should be re-evaluated for SSL connection.176self.deliver_queue_map[self._stream_pos_queued]=stanza# position of the stream when stanza will be successfully and fully sent177self.deliver_key_queue.append(self._stream_pos_queued)178self.stanza_queue=[]
182""" Put the "immidiatedly send" queue content on the wire. Blocks until at least one byte sent."""183ifself.sendbuffer:184try:185# LOCK_QUEUE186sent=self._send(self.sendbuffer)# âÌÏËÉÒÕÀÝÁÑ ÛÔÕÞËÁ!187except:188# UNLOCK_QUEUE189self.set_socket_state(SOCKET_DEAD)190self.DEBUG("Socket error while sending data",'error')191returnself.terminate_stream()192self.DEBUG(`self.fileno()`+' '+self.sendbuffer[:sent],'sent')193self._stream_pos_sent+=sent194self.sendbuffer=self.sendbuffer[sent:]195self._stream_pos_delivered=self._stream_pos_sent# Should be acquired from socket somehow. Take SSL into account.196whileself.deliver_key_queueandself._stream_pos_delivered>self.deliver_key_queue[0]:197delself.deliver_queue_map[self.deliver_key_queue[0]]198self.deliver_key_queue.remove(self.deliver_key_queue[0])
202""" This is callback that is used to pass the received stanza forth to owner's dispatcher203 _if_ the stream is authorised. Otherwise the stanza is just dropped.204 The 'trusted' argument is used to emulate stanza receive.205 This method is used internally.206 """207self._owner.packets+=1208printself._owner.packets209ifself._stream_state==STREAM__OPENEDortrusted:# if the server really should reject all stanzas after he is closed stream (himeself)?210self.DEBUG(stanza.__str__(),'dispatch')211stanza.trusted=trusted212returnself.Dispatcher.dispatch(stanza,self)
215""" This callback is used to detect the stream namespace of incoming stream. Used internally. """216ifnotattrs.has_key('id')ornotattrs['id']:217returnself.terminate_stream(STREAM_INVALID_XML)218self.ID=attrs['id']219ifnotattrs.has_key('version'):self._owner.Dialback(self)
268""" Declare some feature as illegal. Illegal features can not be used.269 Example: BIND feature becomes illegal after Non-SASL auth. """270iffeatureinself.waiting_features:self.waiting_features.remove(feature)
317""" Declare some feature as "negotiating now" to prevent other features from start negotiating. """318ifself.feature_in_process:raise"Starting feature %s over %s !"%(f,self.feature_in_process)319self.feature_in_process=f
322""" Declare some feature as "negotiated" to allow other features start negotiating. """323ifself.feature_in_process<>f:raise"Stopping feature %s instead of %s !"%(f,self.feature_in_process)324self.feature_in_process=None
327""" Change the underlaying socket state.328 Socket starts with SOCKET_UNCONNECTED state329 and then proceeds (possibly) to SOCKET_ALIVE330 and then to SOCKET_DEAD """331ifself._socket_state<newstate:self._socket_state=newstate
334""" Change the session state.335 Session starts with SESSION_NOT_AUTHED state336 and then comes through 337 SESSION_AUTHED, SESSION_BOUND, SESSION_OPENED and SESSION_CLOSED states.338 """339ifself._session_state<newstate:340ifself._session_state<SESSION_AUTHEDand \ 341newstate>=SESSION_AUTHED:self._stream_pos_queued=self._stream_pos_sent342self._session_state=newstate
345""" Change the underlaying XML stream state346 Stream starts with STREAM__NOT_OPENED and then proceeds with347 STREAM__OPENED, STREAM__CLOSING and STREAM__CLOSED states.348 Note that some features (like TLS and SASL)349 requires stream re-start so this state can have non-linear changes. """350ifself._stream_state<newstate:self._stream_state=newstate
1# $Id: __init__.py,v 1.9 2005/03/07 09:34:51 snakeru Exp $ 2 3""" 4All features of xmpppy library contained within separate modules. 5At present there are modules: 6simplexml - XML handling routines 7protocol - jabber-objects (I.e. JID and different stanzas and sub-stanzas) handling routines. 8debug - Jacob Lundquist's debugging module. Very handy if you like colored debug. 9auth - Non-SASL and SASL stuff. You will need it to auth as a client or transport.10transports - low level connection handling. TCP and TLS currently. HTTP support planned.11roster - simple roster for use in clients.12dispatcher - decision-making logic. Handles all hooks. The first who takes control over fresh stanzas.13features - different stuff that didn't worths separating into modules14browser - DISCO server framework. Allows to build dynamic disco tree.15filetransfer - Currently contains only IBB stuff. Can be used for bot-to-bot transfers.1617Most of the classes that is defined in all these modules is an ancestors of 18class PlugIn so they share a single set of methods allowing you to compile 19a featured XMPP client. For every instance of PlugIn class the 'owner' is the class20in what the plug was plugged. While plugging in such instance usually sets some21methods of owner to it's own ones for easy access. All session specific info stored22either in instance of PlugIn or in owner's instance. This is considered unhandy23and there are plans to port 'Session' class from xmppd.py project for storing all24session-related info. Though if you are not accessing instances variables directly25and use only methods for access all values you should not have any problems.2627"""2829importsimplexml,protocol,debug,auth,transports,roster,dispatcher,features,browser,filetransfer,commands30fromclientimport*31fromprotocolimport*32
1## protocol.py 2## 3## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: protocol.py,v 1.58 2007/05/13 17:55:46 normanr Exp $ 16 17""" 18Protocol module contains tools that is needed for processing of 19xmpp-related data structures. 20""" 21 22fromsimplexmlimportNode,ustr 23importtime 24NS_ACTIVITY='http://jabber.org/protocol/activity'# XEP-0108 25NS_ADDRESS='http://jabber.org/protocol/address'# XEP-0033 26NS_ADMIN='http://jabber.org/protocol/admin'# XEP-0133 27NS_ADMIN_ADD_USER=NS_ADMIN+'#add-user'# XEP-0133 28NS_ADMIN_DELETE_USER=NS_ADMIN+'#delete-user'# XEP-0133 29NS_ADMIN_DISABLE_USER=NS_ADMIN+'#disable-user'# XEP-0133 30NS_ADMIN_REENABLE_USER=NS_ADMIN+'#reenable-user'# XEP-0133 31NS_ADMIN_END_USER_SESSION=NS_ADMIN+'#end-user-session'# XEP-0133 32NS_ADMIN_GET_USER_PASSWORD=NS_ADMIN+'#get-user-password'# XEP-0133 33NS_ADMIN_CHANGE_USER_PASSWORD=NS_ADMIN+'#change-user-password'# XEP-0133 34NS_ADMIN_GET_USER_ROSTER=NS_ADMIN+'#get-user-roster'# XEP-0133 35NS_ADMIN_GET_USER_LASTLOGIN=NS_ADMIN+'#get-user-lastlogin'# XEP-0133 36NS_ADMIN_USER_STATS=NS_ADMIN+'#user-stats'# XEP-0133 37NS_ADMIN_EDIT_BLACKLIST=NS_ADMIN+'#edit-blacklist'# XEP-0133 38NS_ADMIN_EDIT_WHITELIST=NS_ADMIN+'#edit-whitelist'# XEP-0133 39NS_ADMIN_REGISTERED_USERS_NUM=NS_ADMIN+'#get-registered-users-num'# XEP-0133 40NS_ADMIN_DISABLED_USERS_NUM=NS_ADMIN+'#get-disabled-users-num'# XEP-0133 41NS_ADMIN_ONLINE_USERS_NUM=NS_ADMIN+'#get-online-users-num'# XEP-0133 42NS_ADMIN_ACTIVE_USERS_NUM=NS_ADMIN+'#get-active-users-num'# XEP-0133 43NS_ADMIN_IDLE_USERS_NUM=NS_ADMIN+'#get-idle-users-num'# XEP-0133 44NS_ADMIN_REGISTERED_USERS_LIST=NS_ADMIN+'#get-registered-users-list'# XEP-0133 45NS_ADMIN_DISABLED_USERS_LIST=NS_ADMIN+'#get-disabled-users-list'# XEP-0133 46NS_ADMIN_ONLINE_USERS_LIST=NS_ADMIN+'#get-online-users-list'# XEP-0133 47NS_ADMIN_ACTIVE_USERS_LIST=NS_ADMIN+'#get-active-users-list'# XEP-0133 48NS_ADMIN_IDLE_USERS_LIST=NS_ADMIN+'#get-idle-users-list'# XEP-0133 49NS_ADMIN_ANNOUNCE=NS_ADMIN+'#announce'# XEP-0133 50NS_ADMIN_SET_MOTD=NS_ADMIN+'#set-motd'# XEP-0133 51NS_ADMIN_EDIT_MOTD=NS_ADMIN+'#edit-motd'# XEP-0133 52NS_ADMIN_DELETE_MOTD=NS_ADMIN+'#delete-motd'# XEP-0133 53NS_ADMIN_SET_WELCOME=NS_ADMIN+'#set-welcome'# XEP-0133 54NS_ADMIN_DELETE_WELCOME=NS_ADMIN+'#delete-welcome'# XEP-0133 55NS_ADMIN_EDIT_ADMIN=NS_ADMIN+'#edit-admin'# XEP-0133 56NS_ADMIN_RESTART=NS_ADMIN+'#restart'# XEP-0133 57NS_ADMIN_SHUTDOWN=NS_ADMIN+'#shutdown'# XEP-0133 58NS_AGENTS='jabber:iq:agents'# XEP-0094 (historical) 59NS_AMP='http://jabber.org/protocol/amp'# XEP-0079 60NS_AMP_ERRORS=NS_AMP+'#errors'# XEP-0079 61NS_AUTH='jabber:iq:auth' 62NS_AVATAR='jabber:iq:avatar'# XEP-0008 (historical) 63NS_BIND='urn:ietf:params:xml:ns:xmpp-bind' 64NS_BROWSE='jabber:iq:browse'# XEP-0011 (historical) 65NS_BYTESTREAM='http://jabber.org/protocol/bytestreams'# XEP-0065 66NS_CAPS='http://jabber.org/protocol/caps'# XEP-0115 67NS_CHATSTATES='http://jabber.org/protocol/chatstates'# XEP-0085 68NS_CLIENT='jabber:client' 69NS_COMMANDS='http://jabber.org/protocol/commands' 70NS_COMPONENT_ACCEPT='jabber:component:accept' 71NS_COMPONENT_1='http://jabberd.jabberstudio.org/ns/component/1.0' 72NS_COMPRESS='http://jabber.org/protocol/compress'# XEP-0138 73NS_DATA='jabber:x:data'# XEP-0004 74NS_DELAY='jabber:x:delay' 75NS_DIALBACK='jabber:server:dialback' 76NS_DISCO='http://jabber.org/protocol/disco'# XEP-0030 77NS_DISCO_INFO=NS_DISCO+'#info'# XEP-0030 78NS_DISCO_ITEMS=NS_DISCO+'#items'# XEP-0030 79NS_ENCRYPTED='jabber:x:encrypted'# XEP-0027 80NS_EVENT='jabber:x:event'# XEP-0022 81NS_FEATURE='http://jabber.org/protocol/feature-neg' 82NS_FILE='http://jabber.org/protocol/si/profile/file-transfer'# XEP-0096 83NS_GATEWAY='jabber:iq:gateway' 84NS_GEOLOC='http://jabber.org/protocol/geoloc'# XEP-0080 85NS_GROUPCHAT='gc-1.0' 86NS_HTTP_BIND='http://jabber.org/protocol/httpbind'# XEP-0124 87NS_IBB='http://jabber.org/protocol/ibb' 88NS_INVISIBLE='presence-invisible'# Jabberd2 89NS_IQ='iq'# Jabberd2 90NS_LAST='jabber:iq:last' 91NS_MESSAGE='message'# Jabberd2 92NS_MOOD='http://jabber.org/protocol/mood'# XEP-0107 93NS_MUC='http://jabber.org/protocol/muc'# XEP-0045 94NS_MUC_ADMIN=NS_MUC+'#admin'# XEP-0045 95NS_MUC_OWNER=NS_MUC+'#owner'# XEP-0045 96NS_MUC_UNIQUE=NS_MUC+'#unique'# XEP-0045 97NS_MUC_USER=NS_MUC+'#user'# XEP-0045 98NS_MUC_REGISTER=NS_MUC+'#register'# XEP-0045 99NS_MUC_REQUEST=NS_MUC+'#request'# XEP-0045100NS_MUC_ROOMCONFIG=NS_MUC+'#roomconfig'# XEP-0045101NS_MUC_ROOMINFO=NS_MUC+'#roominfo'# XEP-0045102NS_MUC_ROOMS=NS_MUC+'#rooms'# XEP-0045103NS_MUC_TRAFIC=NS_MUC+'#traffic'# XEP-0045104NS_OFFLINE='http://jabber.org/protocol/offline'# XEP-0013105NS_PHYSLOC='http://jabber.org/protocol/physloc'# XEP-0112106NS_PRESENCE='presence'# Jabberd2107NS_PRIVACY='jabber:iq:privacy'108NS_PRIVATE='jabber:iq:private'109NS_PUBSUB='http://jabber.org/protocol/pubsub'# XEP-0060110NS_REGISTER='jabber:iq:register'111NS_ROSTER='jabber:iq:roster'112NS_ROSTERX='http://jabber.org/protocol/rosterx'# XEP-0144113NS_RPC='jabber:iq:rpc'# XEP-0009114NS_SASL='urn:ietf:params:xml:ns:xmpp-sasl'115NS_SEARCH='jabber:iq:search'116NS_SERVER='jabber:server'117NS_SESSION='urn:ietf:params:xml:ns:xmpp-session'118NS_SI='http://jabber.org/protocol/si'# XEP-0096119NS_SI_PUB='http://jabber.org/protocol/sipub'# XEP-0137120NS_SIGNED='jabber:x:signed'# XEP-0027121NS_STANZAS='urn:ietf:params:xml:ns:xmpp-stanzas'122NS_STREAMS='http://etherx.jabber.org/streams'123NS_TIME='jabber:iq:time'124NS_TLS='urn:ietf:params:xml:ns:xmpp-tls'125NS_VACATION='http://jabber.org/protocol/vacation'126NS_VCARD='vcard-temp'127NS_VERSION='jabber:iq:version'128NS_WAITINGLIST='http://jabber.org/protocol/waitinglist'# XEP-0130129NS_XHTML_IM='http://jabber.org/protocol/xhtml-im'# XEP-0071130NS_DATA_LAYOUT='http://jabber.org/protocol/xdata-layout'# XEP-0141131NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate'# XEP-0122132NS_XMPP_STREAMS='urn:ietf:params:xml:ns:xmpp-streams'133134xmpp_stream_error_conditions="""135bad-format -- -- -- The entity has sent XML that cannot be processed.136bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.137conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.138connection-timeout -- -- -- The entity has not generated any traffic over the stream for some period of time.139host-gone -- -- -- The value of the 'to' attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server.140host-unknown -- -- -- The value of the 'to' attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server.141improper-addressing -- -- -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value).142internal-server-error -- -- -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream.143invalid-from -- cancel -- -- The JID or hostname provided in a 'from' address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization.144invalid-id -- -- -- The stream ID or dialback ID is invalid or does not match an ID previously provided.145invalid-namespace -- -- -- The streams namespace name is something other than "http://etherx.jabber.org/streams" or the dialback namespace name is something other than "jabber:server:dialback".146invalid-xml -- -- -- The entity has sent invalid XML over the stream to a server that performs validation.147not-authorized -- -- -- The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation.148policy-violation -- -- -- The entity has violated some local service policy.149remote-connection-failed -- -- -- The server is unable to properly connect to a remote resource that is required for authentication or authorization.150resource-constraint -- -- -- The server lacks the system resources necessary to service the stream.151restricted-xml -- -- -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character.152see-other-host -- -- -- The server will not provide service to the initiating entity but is redirecting traffic to another host.153system-shutdown -- -- -- The server is being shut down and all active streams are being closed.154undefined-condition -- -- -- The error condition is not one of those defined by the other conditions in this list.155unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server.156unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server.157unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server.158xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed."""159xmpp_stanza_error_conditions="""160bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.161conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.162feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.163forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action.164gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address.165internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.166item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found.167jid-malformed -- 400 -- modify -- The value of the 'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme.168not-acceptable -- 406 -- cancel -- The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server.169not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action.170not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.171payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required.172recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable.173redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity.174registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required.175remote-server-not-found -- 404 -- cancel -- A remote server or service specified as part or all of the JID of the intended recipient does not exist.176remote-server-timeout -- 504 -- wait -- A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time.177resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request.178service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service.179subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.180undefined-condition -- 500 -- -- 181unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order)."""182sasl_error_conditions="""183aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.184incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data.185invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data.186invalid-mechanism -- -- -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element.187mechanism-too-weak -- -- -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data.188not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data.189temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element."""190191ERRORS,_errorcodes={},{}192forns,errname,errpoolin[(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),193(NS_STANZAS,'ERR',xmpp_stanza_error_conditions),194(NS_SASL,'SASL',sasl_error_conditions)]:195forerrinerrpool.split('\n')[1:]:196cond,code,typ,text=err.split(' -- ')197name=errname+'_'+cond.upper().replace('-','_')198locals()[name]=ns+' '+cond199ERRORS[ns+' '+cond]=[code,typ,text]200ifcode:_errorcodes[code]=cond201delns,errname,errpool,err,cond,code,typ,text202
267""" Constructor. JID can be specified as string (jid argument) or as separate parts.268 Examples:269 JID('node@domain/resource')270 JID(node='node',domain='domain.org')271 """272ifnotjidandnotdomain:raiseValueError('JID must contain at least domain name')273eliftype(jid)==type(self):self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource274elifdomain:self.node,self.domain,self.resource=node,domain,resource275else:276ifjid.find('@')+1:self.node,jid=jid.split('@',1)277else:self.node=''278ifjid.find('/')+1:self.domain,self.resource=jid.split('/',1)279else:self.domain,self.resource=jid,''
302""" Compare the JID to another instance or to string for equality. """303try:other=JID(other)304exceptValueError:return0305returnself.resource==other.resourceandself.__str__(0)==other.__str__(0)
325""" Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.326 to is the value of 'to' attribure, 'typ' - 'type' attribute327 frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition328 timestamp - the time value that needs to be stamped over stanza329 xmlns - namespace of top stanza node330 node - parsed or unparsed stana to be taken as prototype.331 """332ifnotattrs:attrs={}333ifto:attrs['to']=to334iffrm:attrs['from']=frm335iftyp:attrs['type']=typ336Node.__init__(self,tag=name,attrs=attrs,payload=payload,node=node)337ifnotnodeandxmlns:self.setNamespace(xmlns)338ifself['to']:self.setTo(self['to'])339ifself['from']:self.setFrom(self['from'])340ifnodeandtype(self)==type(node)andself.__class__==node.__class__andself.attrs.has_key('id'):delself.attrs['id']341self.timestamp=None342forxinself.getTags('x',namespace=NS_DELAY):343try:344ifnotself.getTimestamp()orx.getAttr('stamp')<self.getTimestamp():self.setTimestamp(x.getAttr('stamp'))345except:pass346iftimestampisnotNone:self.setTimestamp(timestamp)# To auto-timestamp stanza just pass timestamp=''
377""" Return the error-condition (if present) or the textual description of the error (otherwise). """378errtag=self.getTag('error')379iferrtag:380fortaginerrtag.getChildren():381iftag.getName()<>'text':returntag.getName()382returnerrtag.getData()
395"""Set the timestamp. timestamp should be the yyyymmddThhmmss string."""396ifnotval:val=time.strftime('%Y%m%dT%H:%M:%S',time.gmtime())397self.timestamp=val398self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
400""" Return the list of namespaces to which belongs the direct childs of element"""401props=[]402forchildinself.getChildren():403prop=child.getNamespace()404ifpropnotinprops:props.append(prop)405returnprops
414""" Create message object. You can specify recipient, text of message, type of message415 any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.416 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """417Protocol.__init__(self,'message',to=to,typ=typ,attrs=attrs,frm=frm,payload=payload,timestamp=timestamp,xmlns=xmlns,node=node)418ifbody:self.setBody(body)419ifsubject:self.setSubject(subject)
439""" Builds and returns another message object with specified text.440 The to, from and thread properties of new message are pre-set as reply to this message. """441m=Message(to=self.getFrom(),frm=self.getTo(),body=text)442th=self.getThread()443ifth:m.setThread(th)444returnm
449""" Create presence object. You can specify recipient, type of message, priority, show and status values450 any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.451 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """452Protocol.__init__(self,'presence',to=to,typ=typ,attrs=attrs,frm=frm,payload=payload,timestamp=timestamp,xmlns=xmlns,node=node)453ifpriority:self.setPriority(priority)454ifshow:self.setShow(show)455ifstatus:self.setStatus(status)
510""" Create Iq object. You can specify type, query namespace511 any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.512 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """513Protocol.__init__(self,'iq',to=to,typ=typ,attrs=attrs,frm=frm,xmlns=xmlns,node=node)514ifpayload:self.setQueryPayload(payload)515ifqueryNS:self.setQueryNS(queryNS)
541""" Builds and returns another Iq object of specified type.542 The to, from and query child node of new Iq are pre-set as reply to this Iq. """543iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})544ifself.getTag('query'):iq.setQueryNS(self.getQueryNS())545returniq
548""" XMPP-style error element.549 In the case of stanza error should be attached to XMPP stanza.550 In the case of stream-level errors should be used separately. """
552""" Create new error node object.553 Mandatory parameter: name - name of error condition.554 Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol."""555ifERRORS.has_key(name):556cod,type,txt=ERRORS[name]557ns=name.split()[0]558else:cod,ns,type,txt='500',NS_STANZAS,'cancel',''559iftyp:type=typ560ifcode:cod=code561iftext:txt=text562Node.__init__(self,'error',{},[Node(name)])563iftype:self.setAttr('type',type)564ifnotcod:self.setName('stream:error')565iftxt:self.addChild(node=Node(ns+' text',{},[txt]))566ifcod:self.setAttr('code',cod)
571""" Create error reply basing on the received 'node' stanza and the 'error' error condition.572 If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)573 specify the 'reply' argument as false."""574ifreply:Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)575else:Protocol.__init__(self,node=node)576self.setError(error)577ifnode.getType()=='error':self.__str__=self.__dupstr__
579""" Dummy function used as preventor of creating error node in reply to error node.580 I.e. you will not be able to serialise "double" error into string.581 """582return''
585""" This class is used in the DataForm class to describe the single data item.586 If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) 587 then you will need to work with instances of this class. """
589""" Create new data field of specified name,value and type.590 Also 'required','desc' and 'options' fields can be set.591 Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.592 """593Node.__init__(self,'field',node=node)594ifname:self.setVar(name)595iftype(value)in[list,tuple]:self.setValues(value)596elifvalue:self.setValue(value)597iftyp:self.setType(typ)598elifnottypandnotnode:self.setType('text-single')599ifrequired:self.setRequired(required)600iflabel:self.setLabel(label)601ifdesc:self.setDesc(desc)602ifoptions:self.setOptions(options)
630""" Set the values of this field as values-list.631 Replaces all previous filed values! If you need to just add a value - use addValue method."""632whileself.getTag('value'):self.delChild('value')633forvalinlst:self.addValue(val)
643""" Return label-option pairs list associated with this field."""644ret=[]645fortaginself.getTags('option'):ret.append([tag.getAttr('label'),tag.getTagData('value')])646returnret
648""" Set label-option pairs list associated with this field."""649whileself.getTag('option'):self.delChild('option')650foroptinlst:self.addOption(opt)
652""" Add one more label-option pair to this field."""653iftype(opt)in[str,unicode]:self.addChild('option').setTagData('value',opt)654else:self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
669""" DataForm class. Used for manipulating dataforms in XMPP.670 Relevant XEPs: 0004, 0068, 0122.671 Can be used in disco, pub-sub and many other applications."""
673"""674 Create new dataform of type 'typ'. 'data' is the list of DataField675 instances that this dataform contains, 'title' - the title string.676 You can specify the 'node' argument as the other node to be used as677 base for constructing this dataform.678679 title and instructions is optional and SHOULD NOT contain newlines.680 Several instructions MAY be present.681 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )682 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.683 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.684 'title' MAY be included in forms of type "form" and "result"685 """686Node.__init__(self,'x',node=node)687ifnode:688newkids=[]689forninself.getChildren():690ifn.getName()=='field':newkids.append(DataField(node=n))691else:newkids.append(n)692self.kids=newkids693iftyp:self.setType(typ)694self.setNamespace(NS_DATA)695iftitle:self.setTitle(title)696iftype(data)==type({}):697newdata=[]698fornameindata.keys():newdata.append(DataField(name,data[name]))699data=newdata700forchildindata:701iftype(child)in[type(''),type(u'')]:self.addInstructions(child)702elifchild.__class__.__name__=='DataField':self.kids.append(child)703else:self.kids.append(DataField(node=child))
729""" Create if nessessary or get the existing datafield object with name 'name' and return it. """730f=self.getField(name)731iff:returnf732returnself.addChild(node=DataField(name))
734""" Represent dataform as simple dictionary mapping of datafield names to their values."""735ret={}736forfieldinself.getTags('field'):737name=field.getAttr('var')738typ=field.getType()739ifisinstance(typ,(str,unicode))andtyp[-6:]=='-multi':740val=[]741foriinfield.getTags('value'):val.append(i.getData())742else:val=field.getTagData('value')743ret[name]=val744ifself.getTag('instructions'):ret['instructions']=self.getInstructions()745returnret
747""" Simple dictionary interface for getting datafields values by their names."""748item=self.getField(name)749ifitem:returnitem.getValue()750raiseIndexError('No such field')
1## auth.py 2## 3## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: auth.py,v 1.38 2007/08/28 10:03:33 normanr Exp $ 16 17""" 18Provides library with all Non-SASL and SASL authentication mechanisms. 19Can be used both for client and transport authentication. 20""" 21 22fromprotocolimport* 23fromclientimportPlugIn 24importsha,base64,random,dispatcher,re 25 26importmd5
93""" Handler for registering in dispatcher for accepting transport authentication. """ 94ifstanza.getName()=='handshake':self.handshake=1 95else:self.handshake=-1
112""" Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be113 either "success" or "failure". Note that successfull auth will take at least114 two Dispatcher.Process() calls. """115ifself.startsasl:pass116elifself._owner.Dispatcher.Stream.features:117try:self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)118exceptNodeProcessed:pass119else:self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
129""" Used to determine if server supports SASL auth. Used internally. """130ifnotfeats.getTag('mechanisms',namespace=NS_SASL):131self.startsasl='not-supported'132self.DEBUG('SASL not supported by server','error')133return134mecs=[]135formecinfeats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):136mecs.append(mec.getData())137self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)138self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)139self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL)140if"DIGEST-MD5"inmecs:141node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})142elif"PLAIN"inmecs:143sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password)144node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data)])145else:146self.startsasl='failure'147self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')148return149self.startsasl='in-process'150self._owner.send(node.__str__())151raiseNodeProcessed
229""" Determine if server supports resource binding and set some internal attributes accordingly. """230ifnotfeats.getTag('bind',namespace=NS_BIND):231self.bound='failure'232self.DEBUG('Server does not requested binding.','error')233return234iffeats.getTag('session',namespace=NS_SESSION):self.session=1235else:self.session=-1236self.bound=[]
239""" Perform binding. Use provided resource name or random (if not provided). """240whileself.boundisNoneandself._owner.Process(1):pass241ifresource:resource=[Node('resource',payload=[resource])]242else:resource=[]243resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)]))244ifisResultNode(resp):245self.bound.append(resp.getTag('bind').getTagData('jid'))246self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')247jid=JID(resp.getTag('bind').getTagData('jid'))248self._owner.User=jid.getNode()249self._owner.Resource=jid.getResource()250resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})]))251ifisResultNode(resp):252self.DEBUG('Successfully opened session.','ok')253self.session=1254return'ok'255else:256self.DEBUG('Session open failed.','error')257self.session=0258elifresp:self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')259else:260self.DEBUG('Binding failed: timeout expired.','error')261return''
285""" Remove ComponentBind handler from owner's dispatcher. Used internally. """286ifself.needsUnregister:287self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
290""" Determine if server supports resource binding and set some internal attributes accordingly. """291ifnotfeats.getTag('bind',namespace=NS_BIND):292self.bound='failure'293self.DEBUG('Server does not requested binding.','error')294return295iffeats.getTag('session',namespace=NS_SESSION):self.session=1296else:self.session=-1297self.bound=[]
1## features.py 2## 3## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov 4## 5## This program is free software; you can redistribute it and/or modify 6## it under the terms of the GNU General Public License as published by 7## the Free Software Foundation; either version 2, or (at your option) 8## any later version. 9## 10## This program is distributed in the hope that it will be useful, 11## but WITHOUT ANY WARRANTY; without even the implied warranty of 12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13## GNU General Public License for more details. 14 15# $Id: features.py,v 1.24 2006/03/25 05:47:22 snakeru Exp $ 16 17""" 18This module contains variable stuff that is not worth splitting into separate modules. 19Here is: 20 DISCO client and agents-to-DISCO and browse-to-DISCO emulators. 21 IBR and password manager. 22 jabber:iq:privacy methods 23All these methods takes 'disp' first argument that should be already connected 24(and in most cases already authorised) dispatcher instance. 25""" 26 27fromprotocolimport* 28 29REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED' 30 31### DISCO ### http://jabber.org/protocol/disco ### JEP-0030 #################### 32### Browse ### jabber:iq:browse ### JEP-0030 ################################### 33### Agents ### jabber:iq:agents ### JEP-0030 ###################################
35""" Try to obtain info from the remote object. 36 If remote object doesn't support disco fall back to browse (if fb2b is true) 37 and if it doesnt support browse (or fb2b is not true) fall back to agents protocol 38 (if gb2a is true). Returns obtained info. Used internally. """ 39iq=Iq(to=jid,typ='get',queryNS=ns) 40ifnode:iq.setQuerynode(node) 41rep=disp.SendAndWaitForResponse(iq) 42iffb2bandnotisResultNode(rep):rep=disp.SendAndWaitForResponse(Iq(to=jid,typ='get',queryNS=NS_BROWSE))# Fallback to browse 43iffb2aandnotisResultNode(rep):rep=disp.SendAndWaitForResponse(Iq(to=jid,typ='get',queryNS=NS_AGENTS))# Fallback to agents 44ifisResultNode(rep):returnrep.getQueryPayload() 45return[]
48""" Query remote object about any items that it contains. Return items list. """ 49""" According to JEP-0030: 50 query MAY have node attribute 51 item: MUST HAVE jid attribute and MAY HAVE name, node, action attributes. 52 action attribute of item can be either of remove or update value.""" 53ret=[] 54foriin_discover(disp,NS_DISCO_ITEMS,jid,node): 55ifi.getName()=='agent'andi.getTag('name'):i.setAttr('name',i.getTagData('name')) 56ret.append(i.attrs) 57returnret
60""" Query remote object about info that it publishes. Returns identities and features lists.""" 61""" According to JEP-0030: 62 query MAY have node attribute 63 identity: MUST HAVE category and name attributes and MAY HAVE type attribute. 64 feature: MUST HAVE var attribute""" 65identities,features=[],[] 66foriin_discover(disp,NS_DISCO_INFO,jid,node): 67ifi.getName()=='identity':identities.append(i.attrs) 68elifi.getName()=='feature':features.append(i.getAttr('var')) 69elifi.getName()=='agent': 70ifi.getTag('name'):i.setAttr('name',i.getTagData('name')) 71ifi.getTag('description'):i.setAttr('name',i.getTagData('description')) 72identities.append(i.attrs) 73ifi.getTag('groupchat'):features.append(NS_GROUPCHAT) 74ifi.getTag('register'):features.append(NS_REGISTER) 75ifi.getTag('search'):features.append(NS_SEARCH) 76returnidentities,features
80""" Gets registration form from remote host. 81 You can pre-fill the info dictionary. 82 F.e. if you are requesting info on registering user joey than specify 83 info as {'username':'joey'}. See JEP-0077 for details. 84 'disp' must be connected dispatcher instance.""" 85iq=Iq('get',NS_REGISTER,to=host) 86foriininfo.keys():iq.setTagData(i,info[i]) 87ifsync: 88resp=disp.SendAndWaitForResponse(iq) 89_ReceivedRegInfo(disp.Dispatcher,resp,host) 90returnresp 91else:disp.SendAndCallForResponse(iq,_ReceivedRegInfo,{'agent':host})
108""" Perform registration on remote server with provided info.109 disp must be connected dispatcher instance.110 Returns true or false depending on registration result.111 If registration fails you can get additional info from the dispatcher's owner112 attributes lastErrNode, lastErr and lastErrCode.113 """114iq=Iq('set',NS_REGISTER,to=host)115iftype(info)<>type({}):info=info.asDict()116foriininfo.keys():iq.setTag('query').setTagData(i,info[i])117resp=disp.SendAndWaitForResponse(iq)118ifisResultNode(resp):return1
121""" Unregisters with host (permanently removes account).122 disp must be connected and authorized dispatcher instance.123 Returns true on success."""124resp=disp.SendAndWaitForResponse(Iq('set',NS_REGISTER,to=host,payload=[Node('remove')]))125ifisResultNode(resp):return1
128""" Changes password on specified or current (if not specified) server.129 disp must be connected and authorized dispatcher instance.130 Returns true on success."""131ifnothost:host=disp._owner.Server132resp=disp.SendAndWaitForResponse(Iq('set',NS_REGISTER,to=host,payload=[Node('username',payload=[disp._owner.Server]),Node('password',payload=[newpassword])]))133ifisResultNode(resp):return1
140""" Requests privacy lists from connected server.141 Returns dictionary of existing lists on success."""142try:143dict={'lists':[]}144resp=disp.SendAndWaitForResponse(Iq('get',NS_PRIVACY))145ifnotisResultNode(resp):return146forlistinresp.getQueryPayload():147iflist.getName()=='list':dict['lists'].append(list.getAttr('name'))148else:dict[list.getName()]=list.getAttr('name')149returndict150except:pass
153""" Requests specific privacy list listname. Returns list of XML nodes (rules)154 taken from the server responce."""155try:156resp=disp.SendAndWaitForResponse(Iq('get',NS_PRIVACY,payload=[Node('list',{'name':listname})]))157ifisResultNode(resp):returnresp.getQueryPayload()[0]158except:pass
161""" Switches privacy list 'listname' to specified type.162 By default the type is 'active'. Returns true on success."""163iflistname:attrs={'name':listname}164else:attrs={}165resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)]))166ifisResultNode(resp):return1
173""" Set the ruleset. 'list' should be the simpleXML node formatted174 according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )175 Returns true on success."""176resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[list]))177ifisResultNode(resp):return1
When javascript is enabled, this page will redirect URLs of
the form redirect.html#dotted.name to the
documentation for the object with the given fully-qualified
dotted name.
\n"%(tm,nick,text)).encode('koi8-r','replace')
# print time.localtime(tp),nick,text
def messageCB(sess,mess):
nick=mess.getFrom().getResource()
text=mess.getBody()
LOG(mess,nick,text)
roster=[]
def presenceCB(sess,pres):
nick=pres.getFrom().getResource()
text=''
if pres.getType()=='unavailable':
if nick in roster:
text=nick+unicode(' покинул конференцию','koi8-r')
roster.remove(nick)
else:
if nick not in roster:
text=nick+unicode(' пришёл в конференцию','koi8-r')
roster.append(nick)
if text: LOG(pres,nick,text)
if 1:
cl=Client(JID(BOT[0]).getDomain(),debug=[])
cl.connect(proxy=PROXY)
cl.RegisterHandler('message',messageCB)
cl.RegisterHandler('presence',presenceCB)
cl.auth(JID(BOT[0]).getNode(),BOT[1])
p=Presence(to='%s/logger'%CONF[0])
p.setTag('x',namespace=NS_MUC).setTagData('password',CONF[1])
p.getTag('x').addChild('history',{'maxchars':'0','maxstanzas':'0'})
cl.send(p)
while 1:
cl.Process(1)
xmpppy-0.4.1/doc/examples/README.py 0000755 0001750 0000050 00000004713 10731032110 015572 0 ustar norman src #!/usr/bin/python
# -*- coding: koi8-r -*-
from xmpp import *
def presenceHandler(conn,presence_node):
""" Handler for playing a sound when particular contact became online """
targetJID='node@domain.org'
if presence_node.getFrom().bareMatch(targetJID):
# play a sound
pass
def iqHandler(conn,iq_node):
""" Handler for processing some "get" query from custom namespace"""
reply=iq_node.buildReply('result')
# ... put some content into reply node
conn.send(reply)
raise NodeProcessed # This stanza is fully processed
def messageHandler(conn,mess_node): pass
if 1:
"""
Example 1:
Connecting to specified IP address.
Connecting to port 5223 - TLS is pre-started.
Using direct connect.
"""
# Born a client
cl=Client('ejabberd.somedomain.org')
# ...connect it to SSL port directly
if not cl.connect(server=('1.2.3.4',5223)):
raise IOError('Can not connect to server.')
else:
"""
Example 2:
Connecting to server via proxy.
Assuming that servername resolves to jabber server IP.
TLS will be started automatically if available.
"""
# Born a client
cl=Client('jabberd2.somedomain.org')
# ...connect via proxy
if not cl.connect(proxy={'host':'someproxy.somedomain.org','port':'8080','user':'proxyuser','password':'proxyuserpassword'}):
raise IOError('Can not connect to server.')
# ...authorize client
if not cl.auth('jabberuser','jabberuserpassword','optional resource name'):
raise IOError('Can not auth with server.')
# ...register some handlers (if you will register them before auth they will be thrown away)
cl.RegisterHandler('presence',presenceHandler)
cl.RegisterHandler('iq',iqHandler)
cl.RegisterHandler('message',messageHandler)
# ...become available
cl.sendInitPresence()
# ...work some time
cl.Process(1)
# ...if connection is brocken - restore it
if not cl.isConnected(): cl.reconnectAndReauth()
# ...send an ASCII message
cl.send(Message('test@jabber.org','Test message'))
# ...send a national message
cl.send(Message('test@jabber.org',unicode('Проверка связи','koi8-r')))
# ...send another national message
simplexml.ENCODING='koi8-r'
cl.send(Message('test@jabber.org','Проверка связи 2'))
# ...work some more time - collect replies
cl.Process(1)
# ...and then disconnect.
cl.disconnect()
"""
If you have used jabberpy before you will find xmpppy very similar.
See the docs for more info about library features.
"""
xmpppy-0.4.1/doc/examples/xbiff.py 0000755 0001750 0000050 00000005215 10731032110 015731 0 ustar norman src #!/usr/bin/python
# $Id: xbiff.py,v 1.2 2006/10/06 12:30:42 normanr Exp $
import sys,os,xmpp,time,select
class Bot:
def __init__(self,jabber):
self.jabber = jabber
def register_handlers(self):
self.jabber.RegisterHandler('message',self.xmpp_message)
def xmpp_message(self, con, event):
type = event.getType()
fromjid = event.getFrom().getStripped()
if type in ['message', 'chat', None]:
print fromjid+':',event.getBody()
if event.getBody():
p = xmpp.protocol.Presence(to=fromjid,status=event.getBody())
self.jabber.send(p)
p = xmpp.protocol.Presence(to=fromjid,typ='unavailable')
self.jabber.send(p)
def xmpp_connect(self):
con=self.jabber.connect()
if not con:
sys.stderr.write('could not connect!\n')
return False
sys.stderr.write('connected with %s\n'%con)
auth=self.jabber.auth(jid.getNode(),jidparams['password'],resource=jid.getResource())
if not auth:
sys.stderr.write('could not authenticate!\n')
return False
sys.stderr.write('authenticated using %s\n'%auth)
self.register_handlers()
return con
if __name__ == '__main__':
if len(sys.argv) < 1:
print "Syntax: xbiff"
sys.exit(0)
jidparams={}
if os.access(os.environ['HOME']+'/.xbiff',os.R_OK):
for ln in open(os.environ['HOME']+'/.xbiff').readlines():
if not ln[0] in ('#',';'):
key,val=ln.strip().split('=',1)
jidparams[key.lower()]=val
for mandatory in ['jid','password']:
if mandatory not in jidparams.keys():
open(os.environ['HOME']+'/.xbiff','w').write('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\n')
print 'Please point ~/.xbiff config file to valid JID for sending messages.'
sys.exit(0)
jid=xmpp.protocol.JID(jidparams['jid'])
cl=xmpp.Client(jid.getDomain(),debug=[])
bot=Bot(cl)
if not bot.xmpp_connect():
sys.stderr.write("Could not connect to server, or password mismatch!\n")
sys.exit(1)
#cl.SendInitPresence(requestRoster=0) # you may need to uncomment this for old server
socketlist = {cl.Connection._sock:'xmpp'}
online = 1
while online:
(i , o, e) = select.select(socketlist.keys(),[],[],1)
for each in i:
if socketlist[each] == 'xmpp':
cl.Process(1)
else:
raise Exception("Unknown socket type: %s" % repr(socketlist[each]))
#cl.disconnect()
xmpppy-0.4.1/doc/examples/xsend.py 0000755 0001750 0000050 00000002731 10731032110 015754 0 ustar norman src #!/usr/bin/python
# $Id: xsend.py,v 1.8 2006/10/06 12:30:42 normanr Exp $
import sys,os,xmpp,time
if len(sys.argv) < 2:
print "Syntax: xsend JID text"
sys.exit(0)
tojid=sys.argv[1]
text=' '.join(sys.argv[2:])
jidparams={}
if os.access(os.environ['HOME']+'/.xsend',os.R_OK):
for ln in open(os.environ['HOME']+'/.xsend').readlines():
if not ln[0] in ('#',';'):
key,val=ln.strip().split('=',1)
jidparams[key.lower()]=val
for mandatory in ['jid','password']:
if mandatory not in jidparams.keys():
open(os.environ['HOME']+'/.xsend','w').write('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\n')
print 'Please point ~/.xsend config file to valid JID for sending messages.'
sys.exit(0)
jid=xmpp.protocol.JID(jidparams['jid'])
cl=xmpp.Client(jid.getDomain(),debug=[])
con=cl.connect()
if not con:
print 'could not connect!'
sys.exit()
print 'connected with',con
auth=cl.auth(jid.getNode(),jidparams['password'],resource=jid.getResource())
if not auth:
print 'could not authenticate!'
sys.exit()
print 'authenticated using',auth
#cl.SendInitPresence(requestRoster=0) # you may need to uncomment this for old server
id=cl.send(xmpp.protocol.Message(tojid,text))
print 'sent message with id',id
time.sleep(1) # some older servers will not send the message if you disconnect immediately after sending
#cl.disconnect()
xmpppy-0.4.1/doc/examples/xtalk.py 0000755 0001750 0000050 00000005565 10731032110 015766 0 ustar norman src #!/usr/bin/python
# $Id: xtalk.py,v 1.2 2006/10/06 12:30:42 normanr Exp $
import sys,os,xmpp,time,select
class Bot:
def __init__(self,jabber,remotejid):
self.jabber = jabber
self.remotejid = remotejid
def register_handlers(self):
self.jabber.RegisterHandler('message',self.xmpp_message)
def xmpp_message(self, con, event):
type = event.getType()
fromjid = event.getFrom().getStripped()
if type in ['message', 'chat', None] and fromjid == self.remotejid:
sys.stdout.write(event.getBody() + '\n')
def stdio_message(self, message):
m = xmpp.protocol.Message(to=self.remotejid,body=message,typ='chat')
self.jabber.send(m)
pass
def xmpp_connect(self):
con=self.jabber.connect()
if not con:
sys.stderr.write('could not connect!\n')
return False
sys.stderr.write('connected with %s\n'%con)
auth=self.jabber.auth(jid.getNode(),jidparams['password'],resource=jid.getResource())
if not auth:
sys.stderr.write('could not authenticate!\n')
return False
sys.stderr.write('authenticated using %s\n'%auth)
self.register_handlers()
return con
if __name__ == '__main__':
if len(sys.argv) < 2:
print "Syntax: xtalk JID"
sys.exit(0)
tojid=sys.argv[1]
jidparams={}
if os.access(os.environ['HOME']+'/.xtalk',os.R_OK):
for ln in open(os.environ['HOME']+'/.xtalk').readlines():
if not ln[0] in ('#',';'):
key,val=ln.strip().split('=',1)
jidparams[key.lower()]=val
for mandatory in ['jid','password']:
if mandatory not in jidparams.keys():
open(os.environ['HOME']+'/.xtalk','w').write('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\n')
print 'Please point ~/.xtalk config file to valid JID for sending messages.'
sys.exit(0)
jid=xmpp.protocol.JID(jidparams['jid'])
cl=xmpp.Client(jid.getDomain(),debug=[])
bot=Bot(cl,tojid)
if not bot.xmpp_connect():
sys.stderr.write("Could not connect to server, or password mismatch!\n")
sys.exit(1)
#cl.SendInitPresence(requestRoster=0) # you may need to uncomment this for old server
socketlist = {cl.Connection._sock:'xmpp',sys.stdin:'stdio'}
online = 1
while online:
(i , o, e) = select.select(socketlist.keys(),[],[],1)
for each in i:
if socketlist[each] == 'xmpp':
cl.Process(1)
elif socketlist[each] == 'stdio':
msg = sys.stdin.readline().rstrip('\r\n')
bot.stdio_message(msg)
else:
raise Exception("Unknown socket type: %s" % repr(socketlist[each]))
#cl.disconnect()
xmpppy-0.4.1/doc/index.html 0000644 0001750 0000050 00000023146 10731037563 014461 0 ustar norman src
xmpppy: the jabber python project
This library was not designed from scratch. It inherits some code from
jabberpy and have very similar API in many places. Though it is separate
project since it have almost completely different architecture and primarily
aims to work with jabberd2 - the new Open Source Jabber Server.
xmpppy is distributed under the terms of
GNU General Public License
and can be freely redistributed without any charge.
documentation
Documentation is now in the process of heavy development and not yet
finished but most critical docs exist - please feel free to ask any questions if
you will find the docs incomplete (see support section below).
You can look for released versions on
downloads page
or alternatively you can grab the latest version directly from CVS tree
by typing the following commands:
(hit "enter" when you will be prompted for password)
cvs -z3 -d:pserver:anonymous@xmpppy.cvs.sourceforge.net:/cvsroot/xmpppy co xmpppy
You can also browse xmpppy (and several xmpppy-based
projects) CVS online here.
If you have an RSS feed reader, there is
an RSS feed of CVS commits here.
support
If you have any questions about using xmpppy you can join
xmpppy-devel maillist.
Here you can always find the best support, as you can find the developers here.
donations
If you are willing to help you can consult
this article
for how to do it. Thanks!
If you want to donate some money to encourage me to continue work on
library (it helps, really!) you can do it via e-gold system (account 1552795) or via
bank transfer (contact me via jabber or email to get the details).
author
Alexey Nezhdanov
Russian, born 18 Nov 1976.
My timezone is GMT+3
e-mail & Jabber: snake at penza-gsm.ru
ICQ: 19515046
I'm seeking for a job over Internet. It may be jabber-related work or
any other.
Possible directions of work:
Python projects (preferred)
C++ projects
Remote systems administering
My skills:
16 years of programming. Basic -> Pascal -> C++ -> Python
9 years of system administrator work. DOS -> Win 3.1 -> Win95 -> Win98 -> linux2.2 -> linux2.4 -> linux2.6
xmpppy-0.4.1/doc/xmpppy.css 0000644 0001750 0000050 00000002060 10237714546 014527 0 ustar norman src /* xmpppy.css - The stylesheet of the xmpppy homepage
*
* Parts are taken of ickle stylesheet
*
* Copyleft 2005 by Marek Kubica
*
* Version 0.0.20050507
*
*/
/* set a background color, a light grey*/
body {
background-color: #F2F2F2;
}
/* fonts - no serif */
a, p, ul, td, div {
font-family: sans-serif;
}
/* hyperlinks: blue, not decorated, just bold */
a {
text-decoration: none;
color: #00488F;
font-weight: bold;
}
/* the head table, blue like the sf.net logo */
table.head {
width: 100%;
background-color: #00488F;
text-align: right;
border-collapse: collapse;
}
td.head {
padding: 0px;
}
table.content {
padding: 5px;
border-spacing: 30px;
}
td.sflogo {
width: 99%;
}
/* the conentent of the left side fills 80% of the screen */
td.leftside {
width: 80%;
}
/* the links on the right side fill the remaining 20%
* and are displayed on top
*/
td.rightside {
width: 20%;
vertical-align: top;
}
/* not simple bullets, but squares */
ul {
list-style-type: square;
}
/* blockquotes in italic */
blockquote {
font-style: italic;
}
xmpppy-0.4.1/doc/xmpppy_title.png 0000644 0001750 0000050 00000002275 10237716550 015731 0 ustar norman src ┴PNG
IHDR , E г/+a gAMA ╞х7┼И tEXtSoftware Adobe ImageReadyqиe<