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.
- data/.gitignore +5 -4
- data/.rspec +3 -3
- data/.travis.yml +7 -7
- data/CHANGELOG +11 -1
- data/Gemfile +3 -3
- data/README.md +29 -29
- data/Rakefile +70 -70
- data/jabber4r-revive.gemspec +25 -25
- data/lib/jabber4r.rb +38 -33
- data/lib/jabber4r/bosh.rb +21 -0
- data/lib/jabber4r/bosh/authentication.rb +13 -0
- data/lib/jabber4r/bosh/authentication/non_sasl.rb +219 -0
- data/lib/jabber4r/bosh/authentication/sasl.rb +239 -0
- data/lib/jabber4r/bosh/session.rb +144 -0
- data/lib/jabber4r/connection.rb +259 -258
- data/lib/jabber4r/debugger.rb +60 -60
- data/lib/jabber4r/jid.rb +20 -19
- data/lib/jabber4r/protocol.rb +249 -257
- data/lib/jabber4r/protocol/authentication.rb +14 -0
- data/lib/jabber4r/protocol/authentication/non_sasl.rb +138 -0
- data/lib/jabber4r/protocol/authentication/sasl.rb +88 -0
- data/lib/jabber4r/protocol/iq.rb +259 -259
- data/lib/jabber4r/protocol/message.rb +245 -245
- data/lib/jabber4r/protocol/parsed_xml_element.rb +207 -207
- data/lib/jabber4r/protocol/presence.rb +160 -160
- data/lib/jabber4r/protocol/xml_element.rb +143 -143
- data/lib/jabber4r/rexml_1.8_patch.rb +15 -15
- data/lib/jabber4r/roster.rb +38 -38
- data/lib/jabber4r/session.rb +615 -615
- data/lib/jabber4r/version.rb +10 -3
- data/spec/lib/jabber4r/bosh/authentication/non_sasl_spec.rb +79 -0
- data/spec/lib/jabber4r/bosh/authentication/sasl_spec.rb +42 -0
- data/spec/lib/jabber4r/bosh/session_spec.rb +406 -0
- data/spec/lib/jabber4r/bosh_spec.rb +0 -0
- data/spec/lib/jabber4r/connection_spec.rb +174 -174
- data/spec/lib/jabber4r/debugger_spec.rb +35 -35
- data/spec/lib/jabber4r/jid_spec.rb +197 -197
- data/spec/lib/jabber4r/protocol/authentication/non_sasl_spec.rb +79 -0
- data/spec/lib/jabber4r/protocol/authentication/sasl_spec.rb +42 -0
- data/spec/spec_helper.rb +11 -11
- data/spec/support/mocks/tcp_socket_mock.rb +8 -8
- metadata +61 -45
- data/Gemfile.lock +0 -45
- data/lib/jabber4r/bosh_session.rb +0 -224
- 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
|
data/lib/jabber4r/connection.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
@
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
#
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
# to
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
puts error.
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
puts error.
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
#
|
216
|
-
#
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
#
|
241
|
-
#
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|