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.
@@ -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
- This code hasn't been tested at all! (yes, i know it's bad, but I couldn't have rspec working with eventmachine) Feel free to pull, branch, improve {code|specs|tests|docs} and we will merge it!
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
- If you used a version before 0.0.6, please change your routes to use the new router DSL. (See template for more help). It shouldn't be long... sorry for that.
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
 
@@ -73,11 +73,6 @@ module Babylon
73
73
  def self.config
74
74
  @@config
75
75
  end
76
-
77
- ##
78
- # Authentication Error (wrong password/jid combination). Used for Clients and Components
79
- class AuthenticationError < Exception
80
- end
81
-
76
+
82
77
  end
83
78
 
@@ -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, &block)
26
- return super(params, &block) if params["host"] && params["port"]
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, &block)
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
- 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
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
- 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
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
- 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
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
- 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}")
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
- 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
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
- end
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")
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
- 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
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
- # Namespace of the client
185
- def stream_namespace
186
- "jabber:client"
187
- end
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
- @start_stream, @stop_stream = builder.to_xml.split('<paste_content_here/>')
27
- send(@start_stream)
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
- hash = Digest::SHA1::hexdigest(stanza.attributes['id'].content + @password)
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
- @connection_callback.call(self) if @connection_callback
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
@@ -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
- stanza.xpath(@xpath, XpathHelper.new).first ? self : false
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
@@ -14,6 +14,12 @@ module Babylon
14
14
  @routes << {"xpath" => path}
15
15
  self
16
16
  end
17
+
18
+ # Match a css.
19
+ def css(path)
20
+ @routes << {"css" => path}
21
+ self
22
+ end
17
23
 
18
24
  # Set the priority of the last created route.
19
25
  def priority(n)
@@ -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 = YAML.load(config_file)[Babylon.environment]
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(params, &on_connected)
42
+ Babylon::ClientConnection.connect(Babylon.config, self)
47
43
  else # By default, we assume it's a component
48
- Babylon::ComponentConnection.connect(params, &on_connected)
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, &block)
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({:on_connection => block}))
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
- EventMachine::stop_event_loop
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
- @stanza_callback = params[:on_stanza]
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.at("xml-not-well-formed")
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
- @stanza_callback.call(stanza) if @stanza_callback
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
- @last_stanza_sent = node
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
 
@@ -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
- # Room is the "highest" element to (it's parent is the <stream> element)
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.9
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-23 00:00:00 -07:00
12
+ date: 2009-03-25 00:00:00 -07:00
13
13
  default_executable: babylon
14
14
  dependencies: []
15
15