1 # -*- test-case-name: twisted.test.test_xmlstream -*-
3 # Twisted, the Framework of Your Internet
4 # Copyright (C) 2001 Matthew W. Lefkowitz
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of version 2.1 of the GNU Lesser General Public
8 # License as published by the Free Software Foundation.
10 # This library 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 GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 from twisted
.internet
import reactor
, protocol
, defer
20 from twisted
.xish
import utility
23 STREAM_CONNECTED_EVENT
= intern("//event/stream/connected")
24 STREAM_START_EVENT
= intern("//event/stream/start")
25 STREAM_END_EVENT
= intern("//event/stream/end")
26 STREAM_ERROR_EVENT
= intern("//event/stream/error")
27 STREAM_AUTHD_EVENT
= intern("//event/stream/authd")
28 RAWDATA_IN_EVENT
= intern("//event/rawdata/in")
29 RAWDATA_OUT_EVENT
= intern("//event/rawdata/out")
31 def hashPassword(sid
, password
):
32 """Create a SHA1-digest string of a session identifier and password """
34 return sha
.new("%s%s" % (sid
, password
)).hexdigest()
37 """ Base class for business logic of authenticating an XmlStream
39 Subclass this object to enable an XmlStream to authenticate to different
40 types of stream hosts (such as clients, components, etc.).
43 1. The Authenticator MUST dispatch a L{STREAM_AUTHD_EVENT} when the stream
44 has been completely authenticated.
45 2. The Authenticator SHOULD reset all state information when
46 L{associateWithStream} is called.
47 3. The Authenticator SHOULD override L{streamStarted}, and start
51 @type namespace: C{str}
52 @cvar namespace: Default namespace for the XmlStream
55 @cvar version: Version attribute for XmlStream. 0.0 will cause the
56 XmlStream to not include a C{version} attribute in the
59 @type streamHost: C{str}
60 @ivar streamHost: Target host for this stream (used as the 'to' attribute)
62 @type xmlstream: C{XmlStream}
63 @ivar xmlstream: The XmlStream that needs authentication
66 namespace
= 'invalid' # Default namespace for stream
67 version
= 0.0 # Stream version
69 def __init__(self
, streamHost
):
70 self
.streamHost
= streamHost
73 def connectionMade(self
):
75 Called by the XmlStream when the underlying socket connection is
76 in place. This allows the Authenticator to send an initial root
77 element, if it's connecting, or wait for an inbound root from
78 the peer if it's accepting the connection
80 Subclasses can use self.xmlstream.send() with the provided xmlstream
81 parameter to send any initial data to the peer
84 def streamStarted(self
, rootelem
):
86 Called by the XmlStream when it has received a root element from
89 @type rootelem: C{Element}
90 @param rootelem: The root element of the XmlStream received from
94 def associateWithStream(self
, xmlstream
):
96 Called by the XmlStreamFactory when a connection has been made
97 to the requested peer, and an XmlStream object has been
100 The default implementation just saves a handle to the new
103 @type xmlstream: C{XmlStream}
104 @param xmlstream: The XmlStream that will be passing events to this
108 self
.xmlstream
= xmlstream
110 class ConnectAuthenticator(Authenticator
):
111 def connectionMade(self
):
112 # Generate stream header
113 if self
.version
== 1.0:
114 sh
= "<stream:stream xmlns='%s' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>" % \
117 sh
= "<stream:stream xmlns='%s' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % \
118 (self
.namespace
, self
.streamHost
)
119 self
.xmlstream
.send(sh
)
121 class XmlStream(protocol
.Protocol
, utility
.EventDispatcher
):
122 def __init__(self
, authenticator
):
123 utility
.EventDispatcher
.__init
__(self
)
125 self
.authenticator
= authenticator
127 self
.rawDataOutFn
= None
128 self
.rawDataInFn
= None
130 # Reset the authenticator
131 authenticator
.associateWithStream(self
)
133 # Setup watcher for stream errors
134 self
.addObserver("/error[@xmlns='http://etherx.jabber.org/streams']", self
.streamError
)
136 def streamError(self
, errelem
):
137 self
.dispatch(errelem
, STREAM_ERROR_EVENT
)
138 self
.transport
.loseConnection()
140 ### --------------------------------------------------------------
144 ### --------------------------------------------------------------
145 def connectionMade(self
):
147 self
.stream
= domish
.elementStream()
148 self
.stream
.DocumentStartEvent
= self
.onDocumentStart
149 self
.stream
.ElementEvent
= self
.onElement
150 self
.stream
.DocumentEndEvent
= self
.onDocumentEnd
152 self
.dispatch(self
, STREAM_CONNECTED_EVENT
)
154 self
.authenticator
.connectionMade()
156 def dataReceived(self
, buf
):
158 if self
.rawDataInFn
: self
.rawDataInFn(buf
)
159 self
.stream
.parse(buf
)
160 except domish
.ParserError
:
161 self
.dispatch(self
, STREAM_ERROR_EVENT
)
162 self
.transport
.loseConnection()
164 def connectionLost(self
, _
):
165 self
.dispatch(self
, STREAM_END_EVENT
)
168 ### --------------------------------------------------------------
172 ### --------------------------------------------------------------
173 def onDocumentStart(self
, rootelem
):
174 if rootelem
.hasAttribute("id"):
175 self
.sid
= rootelem
["id"] # Extract stream identifier
176 self
.authenticator
.streamStarted(rootelem
) # Notify authenticator
177 self
.dispatch(self
, STREAM_START_EVENT
)
179 def onElement(self
, element
):
180 self
.dispatch(element
)
182 def onDocumentEnd(self
):
183 self
.transport
.loseConnection()
185 def setDispatchFn(self
, fn
):
186 self
.stream
.ElementEvent
= fn
188 def resetDispatchFn(self
):
189 self
.stream
.ElementEvent
= self
.onElement
192 if isinstance(obj
, domish
.Element
):
195 if self
.rawDataOutFn
:
196 self
.rawDataOutFn(obj
)
198 self
.transport
.write(obj
)
201 class XmlStreamFactory(protocol
.ReconnectingClientFactory
):
202 def __init__(self
, authenticator
):
203 self
.authenticator
= authenticator
206 def buildProtocol(self
, _
):
208 # Create the stream and register all the bootstrap observers
209 xs
= XmlStream(self
.authenticator
)
211 for event
, fn
in self
.bootstraps
: xs
.addObserver(event
, fn
)
214 def addBootstrap(self
, event
, fn
):
215 self
.bootstraps
.append((event
, fn
))
217 def removeBootstrap(self
, event
, fn
):
218 self
.bootstraps
.remove((event
, fn
))