julien51-babylon 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +13 -7
- data/lib/babylon.rb +1 -6
- data/lib/babylon/client_connection.rb +95 -95
- data/lib/babylon/component_connection.rb +14 -7
- data/lib/babylon/router.rb +7 -2
- data/lib/babylon/router/dsl.rb +6 -0
- data/lib/babylon/runner.rb +22 -8
- data/lib/babylon/xmpp_connection.rb +14 -17
- data/lib/babylon/xmpp_parser.rb +5 -3
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -8,13 +8,9 @@ This framework can use both an XMPP Component (XEP-0114) and an XMPP Client. How
|
|
8
8
|
|
9
9
|
== FEATURES/PROBLEMS:
|
10
10
|
|
11
|
-
* This hasn't been tested.
|
12
|
-
|
13
11
|
== ROADMAP :
|
14
12
|
|
15
13
|
- Instead of passing the stanzas as Nokogiri:XML:Node elements we should find a way to pass an "agnostic" ruby data-structure, so that app developers don't have to learn/know/understand Nokogiri
|
16
|
-
- HELPERS :
|
17
|
-
* strip jids (...)
|
18
14
|
- Improve the Client Connection to support other authentication than PLAIN SASL
|
19
15
|
|
20
16
|
== SYNOPSIS:
|
@@ -83,11 +79,21 @@ Compared to Rails, we are using accessors (and not @variables assigned in the co
|
|
83
79
|
|
84
80
|
script/component
|
85
81
|
|
86
|
-
== ADDITIONAL INFORMATION
|
87
82
|
|
88
|
-
|
83
|
+
If you just want to make use of the connection classes (Client or Component), you can just call the following :
|
84
|
+
|
85
|
+
Babylon::ClientConnection.connect(params, handler)
|
86
|
+
or,
|
87
|
+
Babylon::ComponentConnection.connect(params, handler)
|
88
|
+
|
89
|
+
where params is a hash for all the necessary information to connect, and handler is an object that will receive the callbacks. Right now 3 callbacks are supported:
|
90
|
+
|
91
|
+
on_connected, on_disconnected and on_stanza
|
92
|
+
|
93
|
+
|
94
|
+
== ADDITIONAL INFORMATION
|
89
95
|
|
90
|
-
|
96
|
+
This code hasn't been 100% tested. Feel free to pull, branch, improve {code|specs|tests|docs} and we will merge it!
|
91
97
|
|
92
98
|
== REQUIREMENTS:
|
93
99
|
|
data/lib/babylon.rb
CHANGED
@@ -9,7 +9,6 @@ module Babylon
|
|
9
9
|
require 'base64'
|
10
10
|
require 'resolv'
|
11
11
|
|
12
|
-
|
13
12
|
attr_reader :binding_iq_id, :session_iq_id
|
14
13
|
|
15
14
|
##
|
@@ -22,8 +21,8 @@ module Babylon
|
|
22
21
|
##
|
23
22
|
# Connects the ClientConnection based on SRV records for the jid's domain, if no host or port has been specified.
|
24
23
|
# In any case, we give priority to the specified host and port.
|
25
|
-
def self.connect(params,
|
26
|
-
return super(params,
|
24
|
+
def self.connect(params, handler = nil)
|
25
|
+
return super(params, handler) if params["host"] && params["port"]
|
27
26
|
|
28
27
|
begin
|
29
28
|
begin
|
@@ -42,7 +41,7 @@ module Babylon
|
|
42
41
|
begin
|
43
42
|
params["host"] = record.target.to_s
|
44
43
|
params["port"] = Integer(record.port)
|
45
|
-
super(params,
|
44
|
+
super(params, handler)
|
46
45
|
# Success
|
47
46
|
break
|
48
47
|
rescue SocketError, Errno::ECONNREFUSED
|
@@ -76,115 +75,116 @@ module Babylon
|
|
76
75
|
# Called upon stanza reception
|
77
76
|
# Marked as connected when the client has been SASLed, authenticated, biund to a resource and when the session has been created
|
78
77
|
def receive_stanza(stanza)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
78
|
+
begin
|
79
|
+
case @state
|
80
|
+
when :connected
|
81
|
+
super # Can be dispatched
|
82
|
+
|
83
|
+
when :wait_for_stream
|
84
|
+
if stanza.name == "stream:stream" && stanza.attributes['id']
|
85
|
+
@state = :wait_for_auth_mechanisms unless @success
|
86
|
+
@state = :wait_for_bind if @success
|
87
|
+
end
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
89
|
+
when :wait_for_auth_mechanisms
|
90
|
+
if stanza.name == "stream:features"
|
91
|
+
if stanza.at("starttls") # we shall start tls
|
92
|
+
starttls = Nokogiri::XML::Node.new("starttls", @outstream)
|
93
|
+
starttls["xmlns"] = "urn:ietf:params:xml:ns:xmpp-tls"
|
94
|
+
send(starttls)
|
95
|
+
@state = :wait_for_proceed
|
96
|
+
elsif stanza.at("mechanisms") # tls is ok
|
97
|
+
if stanza.at("mechanisms/[contains(mechanism,'PLAIN')]")
|
98
|
+
# auth_text = "#{jid.strip}\x00#{jid.node}\x00#{password}"
|
99
|
+
auth = Nokogiri::XML::Node.new("auth", @outstream)
|
100
|
+
auth['mechanism'] = "PLAIN"
|
101
|
+
auth['xmlns'] = "urn:ietf:params:xml:ns:xmpp-sasl"
|
102
|
+
auth.content = Base64::encode64([jid, jid.split("@").first, @password].join("\000")).gsub(/\s/, '')
|
103
|
+
send(auth)
|
104
|
+
@state = :wait_for_success
|
105
|
+
end
|
105
106
|
end
|
106
107
|
end
|
107
|
-
end
|
108
108
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
109
|
+
when :wait_for_success
|
110
|
+
if stanza.name == "success" # Yay! Success
|
111
|
+
@success = true
|
112
|
+
@state = :wait_for_stream
|
113
|
+
@parser.reset
|
114
|
+
send @outstream.root.to_xml.split('<paste_content_here/>').first
|
115
|
+
elsif stanza.name == "failure"
|
116
|
+
if stanza.at("bad-auth") || stanza.at("not-authorized")
|
117
|
+
raise AuthenticationError
|
118
|
+
else
|
119
|
+
end
|
118
120
|
else
|
121
|
+
# Hum Failure...
|
119
122
|
end
|
120
|
-
else
|
121
|
-
# Hum Failure...
|
122
|
-
end
|
123
123
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
124
|
+
when :wait_for_bind
|
125
|
+
if stanza.name == "stream:features"
|
126
|
+
if stanza.at("bind")
|
127
|
+
# Let's build the binding_iq
|
128
|
+
@binding_iq_id = Integer(rand(10000))
|
129
|
+
builder = Nokogiri::XML::Builder.new do
|
130
|
+
iq(:type => "set", :id => @context.binding_iq_id) do
|
131
|
+
bind(:xmlns => "urn:ietf:params:xml:ns:xmpp-bind") do
|
132
|
+
if @context.jid.split("/").size == 2
|
133
|
+
resource(@context.jid.split("/").last)
|
134
|
+
else
|
135
|
+
resource("babylon_client_#{@context.binding_iq_id}")
|
136
|
+
end
|
136
137
|
end
|
137
138
|
end
|
138
139
|
end
|
140
|
+
iq = @outstream.add_child(builder.doc.root)
|
141
|
+
send(iq)
|
142
|
+
@state = :wait_for_confirmed_binding
|
139
143
|
end
|
140
|
-
iq = @outstream.add_child(builder.doc.root)
|
141
|
-
send(iq)
|
142
|
-
@state = :wait_for_confirmed_binding
|
143
144
|
end
|
144
|
-
end
|
145
145
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
146
|
+
when :wait_for_confirmed_binding
|
147
|
+
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @binding_iq_id
|
148
|
+
if stanza.at("jid")
|
149
|
+
jid= stanza.at("jid").text
|
150
|
+
end
|
150
151
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
152
|
+
# And now, we must initiate the session
|
153
|
+
@session_iq_id = Integer(rand(10000))
|
154
|
+
builder = Nokogiri::XML::Builder.new do
|
155
|
+
iq(:type => "set", :id => @context.session_iq_id) do
|
156
|
+
session(:xmlns => "urn:ietf:params:xml:ns:xmpp-session")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
iq = @outstream.add_child(builder.doc.root)
|
160
|
+
send(iq)
|
161
|
+
@state = :wait_for_confirmed_session
|
162
|
+
|
163
|
+
when :wait_for_confirmed_session
|
164
|
+
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @session_iq_id && stanza.at("session")
|
165
|
+
# And now, send a presence!
|
166
|
+
presence = Nokogiri::XML::Node.new("presence", @outstream)
|
167
|
+
send(presence)
|
168
|
+
@handler.on_connected(self) if @handler
|
169
|
+
@state = :connected
|
157
170
|
end
|
158
|
-
end
|
159
|
-
iq = @outstream.add_child(builder.doc.root)
|
160
|
-
send(iq)
|
161
|
-
@state = :wait_for_confirmed_session
|
162
|
-
|
163
|
-
when :wait_for_confirmed_session
|
164
|
-
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @session_iq_id && stanza.at("session")
|
165
|
-
# And now, send a presence!
|
166
|
-
presence = Nokogiri::XML::Node.new("presence", @outstream)
|
167
|
-
send(presence)
|
168
|
-
@connection_callback.call(self) if @connection_callback
|
169
|
-
@state = :connected
|
170
|
-
end
|
171
171
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
172
|
+
when :wait_for_proceed
|
173
|
+
start_tls() # starting TLS
|
174
|
+
@state = :wait_for_stream
|
175
|
+
@parser.reset
|
176
|
+
send @outstream.root.to_xml.split('<paste_content_here/>').first
|
177
|
+
end
|
178
|
+
rescue
|
179
|
+
Babylon.logger.error("#{$!}:\n#{$!.backtrace.join("\n")}")
|
177
180
|
end
|
178
|
-
rescue
|
179
|
-
Babylon.logger.error("#{$!}:\n#{$!.backtrace.join("\n")}")
|
180
181
|
end
|
181
|
-
end
|
182
182
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
183
|
+
##
|
184
|
+
# Namespace of the client
|
185
|
+
def stream_namespace
|
186
|
+
"jabber:client"
|
187
|
+
end
|
188
188
|
|
189
|
-
end
|
189
|
+
end
|
190
190
|
end
|
@@ -23,8 +23,8 @@ module Babylon
|
|
23
23
|
paste_content_here # The stream:stream element should be cut here ;)
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
27
|
-
send(
|
26
|
+
start, stop = builder.to_xml.split('<paste_content_here/>')
|
27
|
+
send(start)
|
28
28
|
end
|
29
29
|
|
30
30
|
##
|
@@ -39,10 +39,7 @@ module Babylon
|
|
39
39
|
if stanza.name == "stream:stream" && stanza.attributes['id']
|
40
40
|
# This means the XMPP session started!
|
41
41
|
# We must send the handshake now.
|
42
|
-
|
43
|
-
handshake = Nokogiri::XML::Node.new("handshake", stanza.document)
|
44
|
-
handshake.content = hash
|
45
|
-
send(handshake)
|
42
|
+
send(handshake(stanza))
|
46
43
|
@state = :wait_for_handshake
|
47
44
|
else
|
48
45
|
raise
|
@@ -50,7 +47,7 @@ module Babylon
|
|
50
47
|
|
51
48
|
when :wait_for_handshake
|
52
49
|
if stanza.name == "handshake"
|
53
|
-
@
|
50
|
+
@handler.on_connected(self) if @handler
|
54
51
|
@state = :connected
|
55
52
|
elsif stanza.name == "stream:error"
|
56
53
|
raise AuthenticationError
|
@@ -67,5 +64,15 @@ module Babylon
|
|
67
64
|
'jabber:component:accept'
|
68
65
|
end
|
69
66
|
|
67
|
+
private
|
68
|
+
|
69
|
+
def handshake(stanza)
|
70
|
+
hash = Digest::SHA1::hexdigest(stanza.attributes['id'].content + @password)
|
71
|
+
handshake = Nokogiri::XML::Node.new("handshake", stanza.document)
|
72
|
+
handshake.content = hash
|
73
|
+
handshake
|
74
|
+
end
|
75
|
+
|
76
|
+
|
70
77
|
end
|
71
78
|
end
|
data/lib/babylon/router.rb
CHANGED
@@ -101,7 +101,8 @@ module Babylon
|
|
101
101
|
raise("No controller given for route") unless params["controller"]
|
102
102
|
raise("No action given for route") unless params["action"]
|
103
103
|
@priority = params["priority"] || 0
|
104
|
-
@xpath = params["xpath"]
|
104
|
+
@xpath = params["xpath"] if params["xpath"]
|
105
|
+
@css = params["css"] if params["css"]
|
105
106
|
@controller = Kernel.const_get("#{params["controller"].capitalize}Controller")
|
106
107
|
@action = params["action"]
|
107
108
|
end
|
@@ -109,7 +110,11 @@ module Babylon
|
|
109
110
|
##
|
110
111
|
# Checks that the route matches the stanzas and calls the the action on the controller
|
111
112
|
def accepts?(stanza)
|
112
|
-
|
113
|
+
if @xpath
|
114
|
+
stanza.xpath(@xpath, XpathHelper.new).first ? self : false
|
115
|
+
elsif @css
|
116
|
+
stanza.css(@css).first ? self : false
|
117
|
+
end
|
113
118
|
end
|
114
119
|
|
115
120
|
end
|
data/lib/babylon/router/dsl.rb
CHANGED
data/lib/babylon/runner.rb
CHANGED
@@ -30,22 +30,18 @@ module Babylon
|
|
30
30
|
|
31
31
|
config_file = File.open('config/config.yaml')
|
32
32
|
|
33
|
-
|
34
33
|
# Caching views in production mode.
|
35
34
|
if Babylon.environment == "production"
|
36
35
|
Babylon.cache_views
|
37
36
|
end
|
38
37
|
|
38
|
+
Babylon.config = YAML.load(config_file)[Babylon.environment]
|
39
39
|
|
40
|
-
Babylon.config
|
41
|
-
|
42
|
-
params, on_connected = Babylon.config.merge({:on_stanza => Babylon::CentralRouter.method(:route)}), Babylon::CentralRouter.method(:connected)
|
43
|
-
|
44
|
-
case Babylon.config["application_type"]
|
40
|
+
case Babylon.config["application_type"]
|
45
41
|
when "client"
|
46
|
-
Babylon::ClientConnection.connect(
|
42
|
+
Babylon::ClientConnection.connect(Babylon.config, self)
|
47
43
|
else # By default, we assume it's a component
|
48
|
-
Babylon::ComponentConnection.connect(
|
44
|
+
Babylon::ComponentConnection.connect(Babylon.config, self)
|
49
45
|
end
|
50
46
|
|
51
47
|
# And finally, let's allow the application to do all it wants to do after we started the EventMachine!
|
@@ -53,5 +49,23 @@ module Babylon
|
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
52
|
+
##
|
53
|
+
# Will be called by the connection class once it is connected to the server.
|
54
|
+
def on_connected(connection)
|
55
|
+
Babylon::CentralRouter.method(:connected)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Will be called by the connection class upon disconnection.
|
60
|
+
def on_disconnected()
|
61
|
+
EventMachine.stop_event_loop
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Will be called by the connection class when it receives and parses a stanza.
|
66
|
+
def on_stanza(stanza)
|
67
|
+
Babylon::CentralRouter.method(:route)
|
68
|
+
end
|
69
|
+
|
56
70
|
end
|
57
71
|
end
|
@@ -8,6 +8,10 @@ module Babylon
|
|
8
8
|
# xml-not-well-formed Exception
|
9
9
|
class XmlNotWellFormed < Exception; end
|
10
10
|
|
11
|
+
##
|
12
|
+
# Authentication Error (wrong password/jid combination). Used for Clients and Components
|
13
|
+
class AuthenticationError < Exception; end
|
14
|
+
|
11
15
|
##
|
12
16
|
# This class is in charge of handling the network connection to the XMPP server.
|
13
17
|
class XmppConnection < EventMachine::Connection
|
@@ -18,9 +22,9 @@ module Babylon
|
|
18
22
|
# Connects the XmppConnection to the right host with the right port. I
|
19
23
|
# It passes itself (as handler) and the configuration
|
20
24
|
# This can very well be overwritten by subclasses.
|
21
|
-
def self.connect(params,
|
25
|
+
def self.connect(params, handler)
|
22
26
|
Babylon.logger.debug("CONNECTING TO #{params["host"]}:#{params["port"]}") # Very low level Logging
|
23
|
-
EventMachine.connect(params["host"], params["port"], self, params.merge({
|
27
|
+
EventMachine.connect(params["host"], params["port"], self, params.merge({"handler" => handler}))
|
24
28
|
end
|
25
29
|
|
26
30
|
def connection_completed
|
@@ -31,45 +35,39 @@ module Babylon
|
|
31
35
|
# Called when the connection is terminated and stops the event loop
|
32
36
|
def unbind()
|
33
37
|
Babylon.logger.debug("DISCONNECTED") # Very low level Logging
|
34
|
-
|
35
|
-
raise NotConnected
|
38
|
+
@handler.on_disconnected(self) if @handler
|
36
39
|
end
|
37
40
|
|
38
41
|
##
|
39
42
|
# Instantiate the Handler (called internally by EventMachine) and attaches a new XmppParser
|
40
43
|
def initialize(params)
|
41
44
|
super()
|
42
|
-
@last_stanza_received = nil
|
43
|
-
@last_stanza_sent = nil
|
44
45
|
@jid = params["jid"]
|
45
46
|
@password = params["password"]
|
46
47
|
@host = params["host"]
|
47
48
|
@port = params["port"]
|
48
|
-
@
|
49
|
-
@connection_callback = params[:on_connection]
|
49
|
+
@handler = params["handler"]
|
50
50
|
@parser = XmppParser.new(&method(:receive_stanza))
|
51
51
|
end
|
52
52
|
|
53
53
|
##
|
54
54
|
# Called when a full stanza has been received and returns it to the central router to be sent to the corresponding controller.
|
55
55
|
def receive_stanza(stanza)
|
56
|
-
@last_stanza_received = stanza
|
57
56
|
Babylon.logger.debug("PARSED : #{stanza.to_xml}")
|
58
57
|
# If not handled by subclass (for authentication)
|
59
58
|
case stanza.name
|
60
59
|
when "stream:error"
|
61
|
-
if stanza.
|
62
|
-
Babylon.logger.error("DISCONNECTED DUE TO MALFORMED STANZA : \n#{@last_stanza_sent}")
|
60
|
+
if !stanza.children.empty? and stanza.children.first.name == "xml-not-well-formed"
|
63
61
|
# <stream:error><xml-not-well-formed xmlns:xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>
|
62
|
+
Babylon.logger.error("DISCONNECTED DUE TO MALFORMED STANZA")
|
64
63
|
raise XmlNotWellFormed
|
65
64
|
end
|
66
65
|
# In any case, we need to close the connection.
|
67
66
|
close_connection
|
68
67
|
else
|
69
|
-
@
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
68
|
+
@handler.on_stanza(stanza) if @handler
|
69
|
+
end
|
70
|
+
end
|
73
71
|
|
74
72
|
##
|
75
73
|
# Sends the Nokogiri::XML data (after converting to string) on the stream. It also appends the right "from" to be the component's JId if none has been mentionned. Eventually it displays this data for debugging purposes.
|
@@ -92,8 +90,7 @@ module Babylon
|
|
92
90
|
##
|
93
91
|
# Sends a node on the "line".
|
94
92
|
def send_node(node)
|
95
|
-
|
96
|
-
node["from"] = jid if !node.attributes["from"] && node.attributes["to"]
|
93
|
+
node["from"] ||= jid if node["to"]
|
97
94
|
send_string(node.to_xml)
|
98
95
|
end
|
99
96
|
|
data/lib/babylon/xmpp_parser.rb
CHANGED
@@ -63,8 +63,8 @@ module Babylon
|
|
63
63
|
@callback.call(e)
|
64
64
|
else
|
65
65
|
# Adding the newly created element to the @elem that is being parsed, or, if no element is being parsed, then we set the @top and the @elem to be this newly created element.
|
66
|
-
#
|
67
|
-
@elem = @elem ? @elem.add_child(e) : (@top = e)
|
66
|
+
# @top is the "highest" element to (it's parent is the <stream> element)
|
67
|
+
@elem = @elem ? @elem.add_child(e) : (@top = @doc.root.add_child(e))
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -74,6 +74,8 @@ module Babylon
|
|
74
74
|
if @elem
|
75
75
|
if @elem == @top
|
76
76
|
@callback.call(@elem)
|
77
|
+
# Remove the element from its content, since we're done with it!
|
78
|
+
@elem.unlink
|
77
79
|
# And the current elem is the next sibling or the root
|
78
80
|
@elem = @top = nil
|
79
81
|
else
|
@@ -92,7 +94,7 @@ module Babylon
|
|
92
94
|
(attrs.size / 2).times do |i|
|
93
95
|
name, value = attrs[2 * i], attrs[2 * i + 1]
|
94
96
|
if name =~ /xmlns/
|
95
|
-
node.add_namespace(name, value)
|
97
|
+
node.add_namespace(name.gsub("xmlns:", "").gsub("xmlns", ""), value)
|
96
98
|
else
|
97
99
|
node.set_attribute name, value
|
98
100
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: julien51-babylon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- julien Genestoux
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-25 00:00:00 -07:00
|
13
13
|
default_executable: babylon
|
14
14
|
dependencies: []
|
15
15
|
|