jabber4r-revive 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/.gitignore +5 -4
  2. data/.rspec +3 -3
  3. data/.travis.yml +7 -7
  4. data/CHANGELOG +11 -1
  5. data/Gemfile +3 -3
  6. data/README.md +29 -29
  7. data/Rakefile +70 -70
  8. data/jabber4r-revive.gemspec +25 -25
  9. data/lib/jabber4r.rb +38 -33
  10. data/lib/jabber4r/bosh.rb +21 -0
  11. data/lib/jabber4r/bosh/authentication.rb +13 -0
  12. data/lib/jabber4r/bosh/authentication/non_sasl.rb +219 -0
  13. data/lib/jabber4r/bosh/authentication/sasl.rb +239 -0
  14. data/lib/jabber4r/bosh/session.rb +144 -0
  15. data/lib/jabber4r/connection.rb +259 -258
  16. data/lib/jabber4r/debugger.rb +60 -60
  17. data/lib/jabber4r/jid.rb +20 -19
  18. data/lib/jabber4r/protocol.rb +249 -257
  19. data/lib/jabber4r/protocol/authentication.rb +14 -0
  20. data/lib/jabber4r/protocol/authentication/non_sasl.rb +138 -0
  21. data/lib/jabber4r/protocol/authentication/sasl.rb +88 -0
  22. data/lib/jabber4r/protocol/iq.rb +259 -259
  23. data/lib/jabber4r/protocol/message.rb +245 -245
  24. data/lib/jabber4r/protocol/parsed_xml_element.rb +207 -207
  25. data/lib/jabber4r/protocol/presence.rb +160 -160
  26. data/lib/jabber4r/protocol/xml_element.rb +143 -143
  27. data/lib/jabber4r/rexml_1.8_patch.rb +15 -15
  28. data/lib/jabber4r/roster.rb +38 -38
  29. data/lib/jabber4r/session.rb +615 -615
  30. data/lib/jabber4r/version.rb +10 -3
  31. data/spec/lib/jabber4r/bosh/authentication/non_sasl_spec.rb +79 -0
  32. data/spec/lib/jabber4r/bosh/authentication/sasl_spec.rb +42 -0
  33. data/spec/lib/jabber4r/bosh/session_spec.rb +406 -0
  34. data/spec/lib/jabber4r/bosh_spec.rb +0 -0
  35. data/spec/lib/jabber4r/connection_spec.rb +174 -174
  36. data/spec/lib/jabber4r/debugger_spec.rb +35 -35
  37. data/spec/lib/jabber4r/jid_spec.rb +197 -197
  38. data/spec/lib/jabber4r/protocol/authentication/non_sasl_spec.rb +79 -0
  39. data/spec/lib/jabber4r/protocol/authentication/sasl_spec.rb +42 -0
  40. data/spec/spec_helper.rb +11 -11
  41. data/spec/support/mocks/tcp_socket_mock.rb +8 -8
  42. metadata +61 -45
  43. data/Gemfile.lock +0 -45
  44. data/lib/jabber4r/bosh_session.rb +0 -224
  45. data/spec/lib/jabber4r/bosh_session_spec.rb +0 -150
@@ -0,0 +1,239 @@
1
+ # coding: utf-8
2
+
3
+ # License: see LICENSE
4
+ # Jabber4R - Jabber Instant Messaging Library for Ruby
5
+ # Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
6
+ # Copyright (C) 2013 Sergey Fedorov <strech_ftf@mail.ru>
7
+
8
+ require "ox"
9
+
10
+ module Jabber::Bosh::Authentication
11
+ # This class provides SASL authentication for BOSH session
12
+ class SASL
13
+ attr_reader :session
14
+ attr_reader :mechanisms
15
+
16
+ def initialize(session)
17
+ @session = session
18
+ end
19
+
20
+ # Internal: Send open stream command to jabber server
21
+ #
22
+ # Raises XMLMalformedError
23
+ # Returns boolean
24
+ def open_stream
25
+ body = Ox::Element.new("body").tap do |element|
26
+ element[:xmlns] = "http://jabber.org/protocol/httpbind"
27
+ element["xmlns:xmpp"] = "urn:xmpp:xbosh"
28
+ element["xmpp:version"] = "1.0"
29
+ element[:content] = "text/xml; charset=utf-8"
30
+ element[:rid] = session.generate_next_rid
31
+ element[:to] = session.domain
32
+ element[:secure] = true
33
+ element[:wait] = 60
34
+ element[:hold] = 1
35
+ end
36
+
37
+ Jabber.debug(%Q[Open (rid="#{body.rid}") new session])
38
+
39
+ response = session.post(Ox.dump body)
40
+ xml = Ox.parse(response.body.tr("'", '"'))
41
+
42
+ define_stream_mechanisms(xml)
43
+
44
+ raise Jabber::XMLMalformedError,
45
+ "Couldn't find <body /> attribute [sid]" if xml[:sid].nil?
46
+
47
+ session.sid = xml.sid
48
+
49
+ true
50
+ end
51
+
52
+ # Internal: Send login request to jabber server
53
+ #
54
+ # password - String the password of jabber user
55
+ #
56
+ # Raises TypeError
57
+ # Raises XMLMalformedError
58
+ # Returns boolean
59
+ def pass_authentication(password)
60
+ # TODO : Define different exception type
61
+ raise TypeError,
62
+ "Server SASL mechanisms not include PLAIN mechanism" unless mechanisms.include?(:plain)
63
+
64
+ body = Ox::Element.new("body").tap do |element|
65
+ element[:xmlns] = 'http://jabber.org/protocol/httpbind'
66
+ element["xmpp:version"] = "1.0"
67
+ element["xmlns:xmpp"] = "urn:xmpp:xbosh"
68
+ element[:content] = "text/xml; charset=utf-8"
69
+ element[:rid] = session.generate_next_rid
70
+ element[:sid] = session.sid
71
+
72
+ element << Query.new(session.jid, password, mechanism: :plain).to_ox
73
+ end
74
+
75
+ Jabber.debug(%Q[Authenticate {SASL} (rid="#{body.rid}" sid="#{body.sid}") in opened session] +
76
+ %Q[ as #{session.jid.node}/#{session.jid.resource}])
77
+
78
+ response = session.post(Ox.dump body)
79
+ xml = Ox.parse(response.body.tr("'", '"'))
80
+
81
+ return false if xml.locate("success").empty?
82
+
83
+ true
84
+ end
85
+
86
+ def restart_stream
87
+ body = Ox::Element.new("body").tap do |element|
88
+ element[:xmlns] = 'http://jabber.org/protocol/httpbind'
89
+ element["xmlns:xmpp"] = "urn:xmpp:xbosh"
90
+ element["xmpp:version"] = "1.0"
91
+ element["xmpp:restart"] = true
92
+ element[:content] = "text/xml; charset=utf-8"
93
+ element[:rid] = session.generate_next_rid
94
+ element[:sid] = session.sid
95
+ element[:to] = session.jid.domain
96
+ end
97
+
98
+ response = session.post(Ox.dump body)
99
+ xml = Ox.parse(response.body.tr("'", '"'))
100
+
101
+ return false if xml.locate("stream:features/bind").empty?
102
+
103
+ true
104
+ end
105
+
106
+ def bind_resource
107
+ bind = Ox::Element.new("bind").tap do |element|
108
+ element[:xmlns] = "urn:ietf:params:xml:ns:xmpp-bind"
109
+
110
+ element << (Ox::Element.new("resource") << session.jid.resource)
111
+ end
112
+
113
+ iq = Ox::Element.new("iq").tap do |element|
114
+ element[:id] = Jabber.gen_random_id
115
+ element[:type] = "set"
116
+ element[:xmlns] = "jabber:client"
117
+
118
+ element << bind
119
+ end
120
+
121
+ body = Ox::Element.new("body").tap do |element|
122
+ element[:xmlns] = 'http://jabber.org/protocol/httpbind'
123
+ element["xmlns:xmpp"] = "urn:xmpp:xbosh"
124
+ element["xmpp:version"] = "1.0"
125
+ element[:content] = "text/xml; charset=utf-8"
126
+ element[:rid] = session.generate_next_rid
127
+ element[:sid] = session.sid
128
+
129
+ element << iq
130
+ end
131
+
132
+ response = session.post(Ox.dump body)
133
+ xml = Ox.parse(response.body.tr("'", '"'))
134
+
135
+ raise Jabber::XMLMalformedError, "Couldn't find xml tag <iq/>" if xml.locate("iq").empty?
136
+ return false unless xml.iq[:type] == "result"
137
+
138
+ true
139
+ end
140
+
141
+ # TODO : Make state machine
142
+ def authenticate(jid, password)
143
+ open_stream
144
+
145
+ return false if pass_authentication(password) == false
146
+ return false if restart_stream == false
147
+
148
+ bind_resource
149
+ end
150
+
151
+ # Public: ...
152
+ #
153
+ # xml - Ox::Element
154
+ #
155
+ # Returns Array[Symbol]
156
+ def define_stream_mechanisms(xml)
157
+ @mechanisms = xml.locate("stream:features/mechanisms/mechanism/*")
158
+ .map(&:downcase).map(&:to_sym)
159
+ end
160
+
161
+ private
162
+ class Query
163
+ # Full list of mechanisms
164
+ # http://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml
165
+ MECHANISMS = [:plain].freeze
166
+
167
+ attr_reader :jid, :password
168
+ attr_reader :mechanism
169
+
170
+ # Public: Creates new SASL authentication object
171
+ #
172
+ # jid - [Jabber::JID|String] the jid of jabber server user
173
+ # password - String the user password
174
+ # options - Hash the authentication options (default: Empty hash)
175
+ # :mechanism - Symbol the name of mechnism to use
176
+ #
177
+ # Examples
178
+ #
179
+ # non_sasl = Jabber::Protocol::Authentication::SASL.new("strech@localhost/res-1", "my-pass-phrase")
180
+ # non_sasl.plain? # => true
181
+ # non_sasl.to_xml # =>
182
+ #
183
+ # <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">biwsbj1qdWxpZXQscj1vTXNUQUF3QUFBQU1BQUFBTlAwVEFBQUFBQUJQVTBBQQ==</auth>
184
+ def initialize(jid, password, options = {})
185
+ raise TypeError,
186
+ "Class(Jabber::JID) or Class(String) expected," +
187
+ " but #{jid.class} was given" unless jid.is_a?(Jabber::JID) || jid.is_a?(String)
188
+
189
+ @jid = jid.is_a?(Jabber::JID) ? jid : Jabber::JID.new(jid)
190
+ @password = password
191
+
192
+ @mechanism = options.fetch(:mechanism, :plain)
193
+
194
+ raise ArgumentError,
195
+ "Unknown authentication mechanism '#{mechanism}'," +
196
+ " available is [#{MECHANISMS * ", "}]" unless MECHANISMS.include?(mechanism)
197
+ end
198
+
199
+ # Public: Is SASL object is for plain authentication
200
+ #
201
+ # Returns boolean
202
+ def plain?
203
+ mechanism == :plain
204
+ end
205
+
206
+ # Public: Create XML string from SASL object
207
+ #
208
+ # Returns String
209
+ def dump
210
+ Ox.dump(send mechanism)
211
+ end
212
+ alias :to_xml :dump
213
+
214
+ # Public: Create Ox::Element from SASL object
215
+ #
216
+ # Returns Ox::Element
217
+ def to_ox
218
+ send(mechanism)
219
+ end
220
+
221
+ private
222
+ # Internal: Make xml object for plain authentication mechanism
223
+ #
224
+ # Returns Ox:Element
225
+ def plain
226
+ Ox::Element.new("auth").tap do |element|
227
+ element[:xmlns] = "urn:ietf:params:xml:ns:xmpp-sasl"
228
+ element[:mechanism] = "PLAIN"
229
+
230
+ element << self.class.generate_plain(jid, password)
231
+ end
232
+ end
233
+
234
+ def self.generate_plain(jid, password)
235
+ ["#{jid.strip}\x00#{jid.node}\x00#{password}"].pack("m").gsub(/\s/, "")
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,144 @@
1
+ # coding: utf-8
2
+
3
+ # License: see LICENSE
4
+ # Jabber4R - Jabber Instant Messaging Library for Ruby
5
+ # Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
6
+ # Copyright (C) 2013 Sergey Fedorov <strech_ftf@mail.ru>
7
+
8
+ require "json"
9
+ require "net/http"
10
+
11
+ module Jabber::Bosh
12
+ # This class provide XMPP Over BOSH
13
+ # http://xmpp.org/extensions/xep-0206.html
14
+ class Session
15
+ # Public: Jabber user login
16
+ attr_reader :jid
17
+ # Public: Http request identifier
18
+ attr_accessor :rid
19
+ # Public: Session identifier
20
+ attr_accessor :sid
21
+
22
+ # Public: Bosh service domain
23
+ # FIXME : Replace to host
24
+ attr_reader :domain
25
+ # Public: Bosh service port
26
+ attr_reader :port
27
+ # Public: Bosh service http-bind uri
28
+ attr_reader :bind_uri
29
+
30
+ # Public: Create new BOSH-session and bind it to jabber http-bind service
31
+ #
32
+ # username - String the login of jabber server user
33
+ # password - String the password of jabber server user
34
+ # options - Hash the options for jabber http-bind service (default: Empty hash)
35
+ # :domain - String the jabber server domain indentificator
36
+ # :port - [String|Fixnum] the port of http-bind endpoint of jabber server
37
+ # :bind_uri - String the http-bind uri
38
+ # :use_sasl - boolean the flag defining authentication method
39
+ #
40
+ # Examples
41
+ #
42
+ # Jabber::Bosh::Session.bind("strech@localhost/res-1", "secret-pass")
43
+ # Jabber::Bosh::Session.bind("strech@localhost/res-1", "secret-pass", domain: "localhost")
44
+ #
45
+ # Raises Jabber::AuthenticationError
46
+ # Returns Jabber::Bosh::Session
47
+ # TODO : Change arguments passing into initialize method
48
+ def self.bind(username, password, options = {})
49
+ domain, port, bind_uri, use_sasl = Jabber::Bosh::DEFAULTS.dup.merge!(options).values
50
+
51
+ session = new(domain, port, bind_uri, use_sasl: use_sasl)
52
+ raise Jabber::AuthenticationError, "Failed to login" unless session.authenticate(username, password)
53
+
54
+ session
55
+ end
56
+
57
+ # Public: Create new BOSH-session (not binded to http-bind service)
58
+ #
59
+ # domain - String the jabber server domain
60
+ # port - [String|Fixnum] the port of http-bind endpoint of jabber server
61
+ # bind_uri - String the http-bind uri
62
+ #
63
+ # Returns Jabber::BoshSession
64
+ def initialize(domain, port, bind_uri, options = {})
65
+ @domain, @port, @bind_uri = domain, port, bind_uri
66
+ @use_sasl = options.fetch(:use_sasl, Jabber::Bosh::DEFAULTS[:use_sasl])
67
+
68
+ @alive = false
69
+ end
70
+
71
+ # Public: Authenticate user in jabber server by his username and password
72
+ # NOTE: This authentication is SASL http://xmpp.org/rfcs/rfc3920.html#sasl
73
+ # or Non-SASL http://xmpp.org/extensions/xep-0078.html
74
+ #
75
+ # Returns boolean
76
+ def authenticate(username, password)
77
+ @jid = username.is_a?(Jabber::JID) ? username : Jabber::JID.new(username)
78
+
79
+ authentication = authentication_technology.new(self)
80
+ @alive = authentication.authenticate(jid, password)
81
+ end
82
+
83
+ # Public: Is BOSH-session active? (no polling consider)
84
+ #
85
+ # Returns boolean
86
+ def alive?
87
+ @alive
88
+ end
89
+
90
+ # Public: Is BOSH-session uses sasl authentication
91
+ #
92
+ # Returns boolean
93
+ def sasl?
94
+ @use_sasl
95
+ end
96
+
97
+ # Public: Represent BOSH-session as json object
98
+ #
99
+ # Returns String
100
+ def to_json
101
+ {jid: jid.to_s, rid: rid, sid: sid}.to_json
102
+ end
103
+
104
+ # Internal: Send HTTP-post request on HTTP-bind uri
105
+ #
106
+ # body - String data, which will be sended
107
+ #
108
+ # Raises Net::HTTPBadResponse
109
+ # Returns Net:HttpResponse
110
+ def post(body)
111
+ request = Net::HTTP::Post.new(bind_uri)
112
+ request.body = body
113
+ request.content_length = request.body.size
114
+ request["Content-Type"] = "text/xml; charset=utf-8"
115
+
116
+ Jabber.debug("Sending POST request - #{body.strip}")
117
+
118
+ response = Net::HTTP.new(domain, port).start { |http| http.request(request) }
119
+
120
+ Jabber.debug("Receiving POST response - #{response.code}: #{response.body.inspect}")
121
+
122
+ unless response.is_a?(Net::HTTPSuccess)
123
+ raise Net::HTTPBadResponse, "Net::HTTPSuccess expected, but #{response.class} was received"
124
+ end
125
+
126
+ response
127
+ end
128
+
129
+ # Internal: Generate next request id for http post request
130
+ #
131
+ # FIXME : Will be replaced to Generator class
132
+ #
133
+ # Returns Fixnum
134
+ def generate_next_rid
135
+ @rid ||= rand(1_000_000_000)
136
+ @rid += 1
137
+ end
138
+
139
+ private
140
+ def authentication_technology
141
+ sasl? ? Jabber::Bosh::Authentication::SASL : Jabber::Bosh::Authentication::NonSASL
142
+ end
143
+ end
144
+ end
@@ -1,258 +1,259 @@
1
- # coding: utf-8
2
-
3
- # License: see LICENSE
4
- # Jabber4R - Jabber Instant Messaging Library for Ruby
5
- # Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
6
-
7
- module Jabber
8
- # The connection class encapsulates the connection to the Jabber
9
- # service including managing the socket and controlling the parsing
10
- # of the Jabber XML stream.
11
- class Connection
12
- DISCONNECTED = 1
13
- CONNECTED = 2
14
-
15
- # Public
16
- attr_reader :host, :port, :status, :input, :output
17
-
18
- # Internal
19
- attr_reader :poll_thread, :parser_thread
20
-
21
- # Internal
22
- attr_reader :filters, :handlers
23
-
24
- # Internal
25
- attr_reader :socket, :parser
26
-
27
- def initialize(host, port = 5222)
28
- @host, @port = host, port
29
-
30
- @handlers, @filters = {}, {}
31
-
32
- @poll_counter = 10
33
- @mutex = Mutex.new
34
-
35
- @status = DISCONNECTED
36
- end
37
-
38
- # Public: Connects to the Jabber server through a TCP Socket and
39
- # starts the Jabber parser.
40
- #
41
- # Returns nothing
42
- def connect
43
- @socket = TCPSocket.new(@host, @port)
44
- @parser = Jabber::Protocol.Parser.new(socket, self)
45
-
46
- register_parsing_thread
47
- register_polling_thread
48
-
49
- @status = CONNECTED
50
- end
51
-
52
- # Internal: Register new parser thread
53
- #
54
- # Returns nothing
55
- def register_parsing_thread
56
- @parser_thread = Thread.new { parser.parse }
57
- end
58
-
59
- # Internal: Register new polling thread
60
- #
61
- # Returns nothing
62
- def register_polling_thread
63
- @poll_thread = Thread.new { poll }
64
- end
65
-
66
- # Public: Closes the connection to the Jabber service
67
- #
68
- # Returns nothing
69
- def close
70
- parser_thread.kill if parser_thread # why if?
71
- poll_thread.kill
72
- socket.close if socket
73
-
74
- @status = DISCONNECTED
75
- end
76
- alias :disconnect :close
77
-
78
- # Public: Returns if this connection is connected to a Jabber service
79
- #
80
- # Returns boolean
81
- def connected?
82
- status == CONNECTED
83
- end
84
-
85
- # Public: Returns if this connection is NOT connected to a Jabber service
86
- #
87
- # Returns boolean
88
- def disconnected?
89
- status == DISCONNECTED
90
- end
91
-
92
- # Public: Adds a filter block to process received XML messages
93
- #
94
- # name - String the name of filter
95
- # block - Block of code
96
- #
97
- # Returns nothing
98
- def add_filter(name, &block)
99
- raise ArgumentError, "Expected block to be given" if block.nil?
100
-
101
- @filters[name] = block
102
- end
103
-
104
- # Public: Removes a filter block
105
- #
106
- # name - String the name of filter
107
- #
108
- # Returns Block of code
109
- def remove_filter(name)
110
- filters.delete(name)
111
- end
112
-
113
- # Public: Receiving xml element, and processing it
114
- # NOTE: Synchonized by Mutex
115
- #
116
- # xml - String the string containing xml
117
- # proc_object - Proc the proc object to call (default: nil)
118
- # block - Block of ruby code
119
- #
120
- # Returns nothing
121
- def send(xml, proc_object = nil, &block)
122
- @mutex.synchronize { write_to_socket(xml, proc_object, &block) }
123
- end
124
-
125
- # Public: Receiving xml element, and processing it
126
- # NOTE: Synchonized by Mutex
127
- #
128
- # xml_element - ParsedXMLElement the received from socket xml element
129
- #
130
- # Returns nothing
131
- def receive(xml)
132
- @mutex.synchronize { process_xml_from_socket(xml) }
133
- end
134
-
135
- # Internal: Sends XML data to the socket and (optionally) waits
136
- # to process received data.
137
- # NOTE: If both habdler and block are given, handler has higher proirity
138
- #
139
- # xml - String the xml data to send
140
- # handler - [Proc|Lambda|#call] the proc object or labda to handle response data (optional)
141
- # block - Block the block of ruby code (optional)
142
- #
143
- # Returns nothing
144
- def write_to_socket(xml, handler = nil, &block)
145
- Jabber.debug("SENDING:\n#{xml}")
146
-
147
- handler = block if handler.nil?
148
- handlers[Thread.current] = handler unless handler.nil?
149
-
150
- socket.write(xml)
151
-
152
- @poll_counter = 10
153
- end
154
-
155
- # Internal: Processes a received ParsedXMLElement and executes
156
- # registered handlers and filters against it
157
- #
158
- # xml - ParsedXMLElement The received element
159
- #
160
- # Returns nothing
161
- def process_xml_from_socket(xml)
162
- sleep 0.1 while wait_for_consume?
163
-
164
- Jabber.debug("RECEIVED:\n#{xml}")
165
-
166
- consume_xml_by_handlers(xml) || consume_xml_by_filters(xml)
167
- end
168
-
169
- # Internal: Processes a received ParsedXMLElement by handlers
170
- #
171
- # xml - ParsedXMLElement The received element
172
- #
173
- # Returns boolean
174
- def consume_xml_by_handlers(xml)
175
- handlers.each do |thread, block|
176
- begin
177
- block.call(xml)
178
-
179
- if xml.element_consumed?
180
- handlers.delete(thread)
181
- thread.wakeup if thread.alive?
182
-
183
- return true
184
- end
185
- rescue Exception => error
186
- puts error.to_s
187
- puts error.backtrace.join("\n")
188
- end
189
- end
190
-
191
- false
192
- end
193
-
194
- # Internal: Processes a received ParsedXMLElement by filters
195
- #
196
- # xml - ParsedXMLElement The received element
197
- #
198
- # Returns boolean
199
- def consume_xml_by_filters(xml)
200
- filters.each_value do |block|
201
- begin
202
- block.call(xml)
203
-
204
- return true if xml.element_consumed?
205
- rescue Exception => error
206
- puts error.to_s
207
- puts error.backtrace.join("\n")
208
- end
209
- end
210
-
211
- false
212
- end
213
-
214
- # Internal: Should we wait for next part of socket data
215
- #
216
- # Returns boolean
217
- def wait_for_consume?
218
- handlers.size.zero? && filters.size.zero?
219
- end
220
-
221
- ##
222
- # Mounts a block to handle exceptions if they occur during the
223
- # poll send. This will likely be the first indication that
224
- # the socket dropped in a Jabber Session.
225
- #
226
- def on_connection_exception(&block)
227
- @exception_block = block
228
- end
229
-
230
- def parse_failure(exception = nil)
231
- Thread.new { @exception_block.call(exception) if @exception_block }
232
- end
233
-
234
- ############################################################################
235
- # All that under needs to be REFACTORED #
236
- ############################################################################
237
-
238
- ##
239
- # Starts a polling thread to send "keep alive" data to prevent
240
- # the Jabber connection from closing for inactivity.
241
- #
242
- def poll
243
- sleep 10
244
- while true
245
- sleep 2
246
- @poll_counter = @poll_counter - 1
247
- if @poll_counter < 0
248
- begin
249
- send(" \t ")
250
- rescue
251
- Thread.new {@exception_block.call if @exception_block}
252
- break
253
- end
254
- end
255
- end
256
- end
257
- end
258
- end
1
+ # coding: utf-8
2
+
3
+ # License: see LICENSE
4
+ # Jabber4R - Jabber Instant Messaging Library for Ruby
5
+ # Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
6
+ # Copyright (C) 2013 Sergey Fedorov <strech_ftf@mail.ru>
7
+
8
+ module Jabber
9
+ # The connection class encapsulates the connection to the Jabber
10
+ # service including managing the socket and controlling the parsing
11
+ # of the Jabber XML stream.
12
+ class Connection
13
+ DISCONNECTED = 1
14
+ CONNECTED = 2
15
+
16
+ # Public
17
+ attr_reader :domain, :port, :status, :input, :output
18
+
19
+ # Internal
20
+ attr_reader :poll_thread, :parser_thread
21
+
22
+ # Internal
23
+ attr_reader :filters, :handlers
24
+
25
+ # Internal
26
+ attr_reader :socket, :parser
27
+
28
+ def initialize(domain, port = 5222)
29
+ @domain, @port = domain, port
30
+
31
+ @handlers, @filters = {}, {}
32
+
33
+ @poll_counter = 10
34
+ @mutex = Mutex.new
35
+
36
+ @status = DISCONNECTED
37
+ end
38
+
39
+ # Public: Connects to the Jabber server through a TCP Socket and
40
+ # starts the Jabber parser.
41
+ #
42
+ # Returns nothing
43
+ def connect
44
+ @socket = TCPSocket.new(@domain, @port)
45
+ @parser = Jabber::Protocol.Parser.new(socket, self)
46
+
47
+ register_parsing_thread
48
+ register_polling_thread
49
+
50
+ @status = CONNECTED
51
+ end
52
+
53
+ # Internal: Register new parser thread
54
+ #
55
+ # Returns nothing
56
+ def register_parsing_thread
57
+ @parser_thread = Thread.new { parser.parse }
58
+ end
59
+
60
+ # Internal: Register new polling thread
61
+ #
62
+ # Returns nothing
63
+ def register_polling_thread
64
+ @poll_thread = Thread.new { poll }
65
+ end
66
+
67
+ # Public: Closes the connection to the Jabber service
68
+ #
69
+ # Returns nothing
70
+ def close
71
+ parser_thread.kill if parser_thread # why if?
72
+ poll_thread.kill
73
+ socket.close if socket
74
+
75
+ @status = DISCONNECTED
76
+ end
77
+ alias :disconnect :close
78
+
79
+ # Public: Returns if this connection is connected to a Jabber service
80
+ #
81
+ # Returns boolean
82
+ def connected?
83
+ status == CONNECTED
84
+ end
85
+
86
+ # Public: Returns if this connection is NOT connected to a Jabber service
87
+ #
88
+ # Returns boolean
89
+ def disconnected?
90
+ status == DISCONNECTED
91
+ end
92
+
93
+ # Public: Adds a filter block to process received XML messages
94
+ #
95
+ # name - String the name of filter
96
+ # block - Block of code
97
+ #
98
+ # Returns nothing
99
+ def add_filter(name, &block)
100
+ raise ArgumentError, "Expected block to be given" if block.nil?
101
+
102
+ @filters[name] = block
103
+ end
104
+
105
+ # Public: Removes a filter block
106
+ #
107
+ # name - String the name of filter
108
+ #
109
+ # Returns Block of code
110
+ def remove_filter(name)
111
+ filters.delete(name)
112
+ end
113
+
114
+ # Public: Receiving xml element, and processing it
115
+ # NOTE: Synchonized by Mutex
116
+ #
117
+ # xml - String the string containing xml
118
+ # proc_object - Proc the proc object to call (default: nil)
119
+ # block - Block of ruby code
120
+ #
121
+ # Returns nothing
122
+ def send(xml, proc_object = nil, &block)
123
+ @mutex.synchronize { write_to_socket(xml, proc_object, &block) }
124
+ end
125
+
126
+ # Public: Receiving xml element, and processing it
127
+ # NOTE: Synchonized by Mutex
128
+ #
129
+ # xml_element - ParsedXMLElement the received from socket xml element
130
+ #
131
+ # Returns nothing
132
+ def receive(xml)
133
+ @mutex.synchronize { process_xml_from_socket(xml) }
134
+ end
135
+
136
+ # Internal: Sends XML data to the socket and (optionally) waits
137
+ # to process received data.
138
+ # NOTE: If both habdler and block are given, handler has higher proirity
139
+ #
140
+ # xml - String the xml data to send
141
+ # handler - [Proc|Lambda|#call] the proc object or labda to handle response data (optional)
142
+ # block - Block the block of ruby code (optional)
143
+ #
144
+ # Returns nothing
145
+ def write_to_socket(xml, handler = nil, &block)
146
+ Jabber.debug("SENDING:\n#{xml}")
147
+
148
+ handler = block if handler.nil?
149
+ handlers[Thread.current] = handler unless handler.nil?
150
+
151
+ socket.write(xml)
152
+
153
+ @poll_counter = 10
154
+ end
155
+
156
+ # Internal: Processes a received ParsedXMLElement and executes
157
+ # registered handlers and filters against it
158
+ #
159
+ # xml - ParsedXMLElement The received element
160
+ #
161
+ # Returns nothing
162
+ def process_xml_from_socket(xml)
163
+ sleep 0.1 while wait_for_consume?
164
+
165
+ Jabber.debug("RECEIVED:\n#{xml}")
166
+
167
+ consume_xml_by_handlers(xml) || consume_xml_by_filters(xml)
168
+ end
169
+
170
+ # Internal: Processes a received ParsedXMLElement by handlers
171
+ #
172
+ # xml - ParsedXMLElement The received element
173
+ #
174
+ # Returns boolean
175
+ def consume_xml_by_handlers(xml)
176
+ handlers.each do |thread, block|
177
+ begin
178
+ block.call(xml)
179
+
180
+ if xml.element_consumed?
181
+ handlers.delete(thread)
182
+ thread.wakeup if thread.alive?
183
+
184
+ return true
185
+ end
186
+ rescue Exception => error
187
+ puts error.to_s
188
+ puts error.backtrace.join("\n")
189
+ end
190
+ end
191
+
192
+ false
193
+ end
194
+
195
+ # Internal: Processes a received ParsedXMLElement by filters
196
+ #
197
+ # xml - ParsedXMLElement The received element
198
+ #
199
+ # Returns boolean
200
+ def consume_xml_by_filters(xml)
201
+ filters.each_value do |block|
202
+ begin
203
+ block.call(xml)
204
+
205
+ return true if xml.element_consumed?
206
+ rescue Exception => error
207
+ puts error.to_s
208
+ puts error.backtrace.join("\n")
209
+ end
210
+ end
211
+
212
+ false
213
+ end
214
+
215
+ # Internal: Should we wait for next part of socket data
216
+ #
217
+ # Returns boolean
218
+ def wait_for_consume?
219
+ handlers.size.zero? && filters.size.zero?
220
+ end
221
+
222
+ ##
223
+ # Mounts a block to handle exceptions if they occur during the
224
+ # poll send. This will likely be the first indication that
225
+ # the socket dropped in a Jabber Session.
226
+ #
227
+ def on_connection_exception(&block)
228
+ @exception_block = block
229
+ end
230
+
231
+ def parse_failure(exception = nil)
232
+ Thread.new { @exception_block.call(exception) if @exception_block }
233
+ end
234
+
235
+ ############################################################################
236
+ # All that under needs to be REFACTORED #
237
+ ############################################################################
238
+
239
+ ##
240
+ # Starts a polling thread to send "keep alive" data to prevent
241
+ # the Jabber connection from closing for inactivity.
242
+ #
243
+ def poll
244
+ sleep 10
245
+ while true
246
+ sleep 2
247
+ @poll_counter = @poll_counter - 1
248
+ if @poll_counter < 0
249
+ begin
250
+ send(" \t ")
251
+ rescue
252
+ Thread.new {@exception_block.call if @exception_block}
253
+ break
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end