xmpp4r 0.3
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/COPYING +340 -0
- data/ChangeLog +28 -0
- data/LICENSE +59 -0
- data/README +20 -0
- data/Rakefile +103 -0
- data/UPDATING +40 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
- data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
- data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
- data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
- data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
- data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
- data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
- data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
- data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
- data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
- data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
- data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
- data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
- data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
- data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
- data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
- data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
- data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
- data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
- data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
- data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
- data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
- data/lib/callbacks.rb +122 -0
- data/lib/xmpp4r/authenticationfailure.rb +13 -0
- data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
- data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
- data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
- data/lib/xmpp4r/bytestreams.rb +11 -0
- data/lib/xmpp4r/client.rb +249 -0
- data/lib/xmpp4r/component.rb +103 -0
- data/lib/xmpp4r/connection.rb +166 -0
- data/lib/xmpp4r/dataforms/x/data.rb +248 -0
- data/lib/xmpp4r/dataforms.rb +1 -0
- data/lib/xmpp4r/debuglog.rb +34 -0
- data/lib/xmpp4r/delay/x/delay.rb +100 -0
- data/lib/xmpp4r/delay.rb +1 -0
- data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
- data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
- data/lib/xmpp4r/discovery.rb +2 -0
- data/lib/xmpp4r/error.rb +230 -0
- data/lib/xmpp4r/errorexception.rb +32 -0
- data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
- data/lib/xmpp4r/feature_negotiation.rb +1 -0
- data/lib/xmpp4r/idgenerator.rb +37 -0
- data/lib/xmpp4r/iq.rb +229 -0
- data/lib/xmpp4r/jid.rb +167 -0
- data/lib/xmpp4r/message.rb +171 -0
- data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
- data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
- data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
- data/lib/xmpp4r/muc/x/muc.rb +98 -0
- data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
- data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
- data/lib/xmpp4r/muc.rb +6 -0
- data/lib/xmpp4r/presence.rb +255 -0
- data/lib/xmpp4r/query.rb +43 -0
- data/lib/xmpp4r/rexmladdons.rb +826 -0
- data/lib/xmpp4r/roster/helper/roster.rb +514 -0
- data/lib/xmpp4r/roster/iq/roster.rb +244 -0
- data/lib/xmpp4r/roster/x/roster.rb +155 -0
- data/lib/xmpp4r/roster.rb +4 -0
- data/lib/xmpp4r/sasl.rb +167 -0
- data/lib/xmpp4r/stream.rb +543 -0
- data/lib/xmpp4r/streamparser.rb +77 -0
- data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
- data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
- data/lib/xmpp4r/vcard.rb +3 -0
- data/lib/xmpp4r/version/helper/responder.rb +71 -0
- data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
- data/lib/xmpp4r/version/iq/version.rb +118 -0
- data/lib/xmpp4r/version.rb +3 -0
- data/lib/xmpp4r/x.rb +43 -0
- data/lib/xmpp4r/xmlstanza.rb +174 -0
- data/lib/xmpp4r/xmpp4r.rb +16 -0
- data/lib/xmpp4r.rb +122 -0
- data/setup.rb +1360 -0
- data/test/bytestreams/tc_ibb.rb +186 -0
- data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
- data/test/delay/tc_xdelay.rb +51 -0
- data/test/lib/clienttester.rb +110 -0
- data/test/muc/tc_muc_mucclient.rb +569 -0
- data/test/muc/tc_muc_simplemucclient.rb +72 -0
- data/test/roster/.tc_helper.rb.swp +0 -0
- data/test/roster/tc_helper.rb +389 -0
- data/test/roster/tc_iqqueryroster.rb +140 -0
- data/test/roster/tc_xroster.rb +70 -0
- data/test/tc_callbacks.rb +128 -0
- data/test/tc_class_names.rb +129 -0
- data/test/tc_client.rb +30 -0
- data/test/tc_error.rb +103 -0
- data/test/tc_idgenerator.rb +30 -0
- data/test/tc_iq.rb +109 -0
- data/test/tc_iqquery.rb +31 -0
- data/test/tc_jid.rb +202 -0
- data/test/tc_message.rb +114 -0
- data/test/tc_presence.rb +148 -0
- data/test/tc_stream.rb +182 -0
- data/test/tc_streamError.rb +87 -0
- data/test/tc_streamSend.rb +59 -0
- data/test/tc_streamThreaded.rb +168 -0
- data/test/tc_xmlstanza.rb +76 -0
- data/test/ts_xmpp4r.rb +34 -0
- data/test/vcard/tc_iqvcard.rb +52 -0
- data/test/version/tc_helper.rb +46 -0
- data/test/version/tc_iqqueryversion.rb +96 -0
- data/tools/doctoweb.bash +30 -0
- data/tools/gen_requires.bash +10 -0
- metadata +232 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
|
4
|
+
|
|
5
|
+
require 'xmpp4r/query'
|
|
6
|
+
|
|
7
|
+
module Jabber
|
|
8
|
+
module Roster
|
|
9
|
+
##
|
|
10
|
+
# Class for handling roster updates
|
|
11
|
+
#
|
|
12
|
+
# You must do 'client.send(Iq.new_rosterget)' or else you will
|
|
13
|
+
# have nothing to put in receive_iq()
|
|
14
|
+
#
|
|
15
|
+
# You must require 'xmpp4r/rosterquery' to use this class
|
|
16
|
+
# as its functionality is not needed for a working XMPP implementation.
|
|
17
|
+
# This will make [IqQuery] convert all Queries with namespace 'jabber:iq:roster'
|
|
18
|
+
# to [IqQueryRoster]
|
|
19
|
+
#
|
|
20
|
+
# This <query/> contains multiple <item/> children. See RosterItem.
|
|
21
|
+
class IqQueryRoster < IqQuery
|
|
22
|
+
##
|
|
23
|
+
# Create a new <query xmlns='jabber:iq:roster'/>
|
|
24
|
+
# stream:: [Stream] Stream to handle
|
|
25
|
+
def initialize
|
|
26
|
+
super
|
|
27
|
+
add_namespace('jabber:iq:roster')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Add an element to the roster
|
|
32
|
+
#
|
|
33
|
+
# Converts <item/> elements to RosterItem
|
|
34
|
+
#
|
|
35
|
+
# Previous RosterItems with the same JID will
|
|
36
|
+
# *not* be deleted!
|
|
37
|
+
def typed_add(element)
|
|
38
|
+
if element.kind_of?(REXML::Element) && (element.name == 'item')
|
|
39
|
+
item = RosterItem::new.import(element)
|
|
40
|
+
super(item)
|
|
41
|
+
else
|
|
42
|
+
super(element)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Iterate through all items
|
|
48
|
+
# &block:: Yield for every [RosterItem]
|
|
49
|
+
def each(&block)
|
|
50
|
+
each_element { |item|
|
|
51
|
+
# XPath won't work here as it's missing a prefix...
|
|
52
|
+
yield(item) if item.kind_of?(RosterItem)
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Get roster item by JID
|
|
58
|
+
# jid:: [JID] or [Nil]
|
|
59
|
+
# result:: [RosterItem]
|
|
60
|
+
def [](jid)
|
|
61
|
+
each { |item|
|
|
62
|
+
return(item) if item.jid == jid
|
|
63
|
+
}
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Get all items
|
|
69
|
+
# result:: [Array] of [RosterItem]
|
|
70
|
+
def to_a
|
|
71
|
+
a = []
|
|
72
|
+
each { |item|
|
|
73
|
+
a.push(item)
|
|
74
|
+
}
|
|
75
|
+
a
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Update roster by <iq/> stanza
|
|
80
|
+
# (to be fed by an iq_callback)
|
|
81
|
+
# iq:: [Iq] Containing new roster
|
|
82
|
+
# filter:: [Boolean] If false import non-roster-like results too
|
|
83
|
+
def receive_iq(iq, filter=true)
|
|
84
|
+
if filter && (((iq.type != :set) && (iq.type != :result)) || (iq.queryns != 'jabber:iq:roster'))
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
import(iq.query)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Output for "p"
|
|
93
|
+
#
|
|
94
|
+
# JIDs of all contained [RosterItem] elements are joined with a comma
|
|
95
|
+
# result:: [String]
|
|
96
|
+
def inspect
|
|
97
|
+
jids = to_a.collect { |item| item.jid.inspect }
|
|
98
|
+
jids.join(', ')
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# Class containing the <item/> elements of the roster
|
|
104
|
+
#
|
|
105
|
+
# The 'name' attribute has been renamed to 'iname' here
|
|
106
|
+
# as 'name' is already used by REXML::Element for the
|
|
107
|
+
# element's name. It's still name='...' in XML.
|
|
108
|
+
class RosterItem < REXML::Element
|
|
109
|
+
##
|
|
110
|
+
# Construct a new roster item
|
|
111
|
+
# jid:: [JID] Jabber ID
|
|
112
|
+
# iname:: [String] Name in the roster
|
|
113
|
+
# subscription:: [Symbol] Type of subscription (see RosterItem#subscription=)
|
|
114
|
+
# ask:: [Symbol] or [Nil] Can be :subscribe
|
|
115
|
+
def initialize(jid=nil, iname=nil, subscription=nil, ask=nil)
|
|
116
|
+
super('item')
|
|
117
|
+
self.jid = jid
|
|
118
|
+
self.iname = iname
|
|
119
|
+
self.subscription = subscription
|
|
120
|
+
self.ask = ask
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# Create new RosterItem from REXML::Element
|
|
125
|
+
# item:: [REXML::Element] source element to copy attributes and children from
|
|
126
|
+
def RosterItem.import(item)
|
|
127
|
+
RosterItem::new.import(item)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Get name of roster item
|
|
132
|
+
#
|
|
133
|
+
# names can be set by the roster's owner himself
|
|
134
|
+
# return:: [String]
|
|
135
|
+
def iname
|
|
136
|
+
attributes['name']
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# Set name of roster item
|
|
141
|
+
# val:: [String] Name for this item
|
|
142
|
+
def iname=(val)
|
|
143
|
+
attributes['name'] = val
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
##
|
|
147
|
+
# Get JID of roster item
|
|
148
|
+
# Resource of the JID will _not_ be stripped
|
|
149
|
+
# return:: [JID]
|
|
150
|
+
def jid
|
|
151
|
+
(a = attributes['jid']) ? JID::new(a) : nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
##
|
|
155
|
+
# Set JID of roster item
|
|
156
|
+
# val:: [JID] or nil
|
|
157
|
+
def jid=(val)
|
|
158
|
+
attributes['jid'] = val.nil? ? nil : val.to_s
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
##
|
|
162
|
+
# Get subscription type of roster item
|
|
163
|
+
# result:: [Symbol] or [Nil] The following values are valid according to RFC3921:
|
|
164
|
+
# * :both
|
|
165
|
+
# * :from
|
|
166
|
+
# * :none
|
|
167
|
+
# * :remove
|
|
168
|
+
# * :to
|
|
169
|
+
def subscription
|
|
170
|
+
case attributes['subscription']
|
|
171
|
+
when 'both' then :both
|
|
172
|
+
when 'from' then :from
|
|
173
|
+
when 'none' then :none
|
|
174
|
+
when 'remove' then :remove
|
|
175
|
+
when 'to' then :to
|
|
176
|
+
else nil
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
##
|
|
181
|
+
# Set subscription type of roster item
|
|
182
|
+
# val:: [Symbol] or [Nil] See subscription for possible Symbols
|
|
183
|
+
def subscription=(val)
|
|
184
|
+
case val
|
|
185
|
+
when :both then attributes['subscription'] = 'both'
|
|
186
|
+
when :from then attributes['subscription'] = 'from'
|
|
187
|
+
when :none then attributes['subscription'] = 'none'
|
|
188
|
+
when :remove then attributes['subscription'] = 'remove'
|
|
189
|
+
when :to then attributes['subscription'] = 'to'
|
|
190
|
+
else attributes['subscription'] = nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
##
|
|
195
|
+
# Get if asking for subscription
|
|
196
|
+
# result:: [Symbol] nil or :subscribe
|
|
197
|
+
def ask
|
|
198
|
+
case attributes['ask']
|
|
199
|
+
when 'subscribe' then :subscribe
|
|
200
|
+
else nil
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
##
|
|
205
|
+
# Set if asking for subscription
|
|
206
|
+
# val:: [Symbol] nil or :subscribe
|
|
207
|
+
def ask=(val)
|
|
208
|
+
case val
|
|
209
|
+
when :subscribe then attributes['ask'] = 'subscribe'
|
|
210
|
+
else attributes['ask'] = nil
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
##
|
|
215
|
+
# Get groups the item belongs to
|
|
216
|
+
# result:: [Array] of [String] The groups
|
|
217
|
+
def groups
|
|
218
|
+
result = []
|
|
219
|
+
each_element('group') { |group|
|
|
220
|
+
result.push(group.text)
|
|
221
|
+
}
|
|
222
|
+
result
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
##
|
|
226
|
+
# Set groups the item belongs to,
|
|
227
|
+
# deletes old groups first.
|
|
228
|
+
#
|
|
229
|
+
# See JEP 0083 for nested groups
|
|
230
|
+
# ary:: [Array] New groups, duplicate values will be removed
|
|
231
|
+
def groups=(ary)
|
|
232
|
+
# Delete old group elements
|
|
233
|
+
delete_elements('group')
|
|
234
|
+
|
|
235
|
+
# Add new group elements
|
|
236
|
+
ary.uniq.each { |group|
|
|
237
|
+
add_element('group').text = group
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
IqQuery.add_namespaceclass('jabber:iq:roster', IqQueryRoster)
|
|
243
|
+
end #Module Roster
|
|
244
|
+
end #Module Jabber
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
|
4
|
+
|
|
5
|
+
require 'xmpp4r/x'
|
|
6
|
+
require 'xmpp4r/jid'
|
|
7
|
+
|
|
8
|
+
module Jabber
|
|
9
|
+
module Roster
|
|
10
|
+
##
|
|
11
|
+
# Implementation of JEP-0144
|
|
12
|
+
# for <tt><x xmlns='http://jabber.org/protocol/rosterx'/></tt>
|
|
13
|
+
# attached to <tt><message/></tt> stanzas
|
|
14
|
+
#
|
|
15
|
+
# Should be backwards compatible to JEP-0093,
|
|
16
|
+
# as only action attribute of roster items are missing there.
|
|
17
|
+
# Pay attention to the namespace which is <tt>jabber:x:roster</tt>
|
|
18
|
+
# for JEP-0093!
|
|
19
|
+
class XRoster < X
|
|
20
|
+
##
|
|
21
|
+
# Initialize a new XRoster element
|
|
22
|
+
def initialize
|
|
23
|
+
super()
|
|
24
|
+
add_namespace('http://jabber.org/protocol/rosterx')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Add an element to the roster attachment
|
|
29
|
+
#
|
|
30
|
+
# Converts <item/> elements to XRosterItem
|
|
31
|
+
def typed_add(element)
|
|
32
|
+
if element.kind_of?(REXML::Element) && (element.name == 'item')
|
|
33
|
+
super(XRosterItem::new.import(element))
|
|
34
|
+
else
|
|
35
|
+
super(element)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end #Class XRoster
|
|
39
|
+
|
|
40
|
+
X.add_namespaceclass('jabber:x:roster', XRoster)
|
|
41
|
+
X.add_namespaceclass('http://jabber.org/protocol/rosterx', XRoster)
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Class containing an <item/> element
|
|
45
|
+
#
|
|
46
|
+
# The 'name' attribute has been renamed to 'iname' here
|
|
47
|
+
# as 'name' is already used by REXML::Element for the
|
|
48
|
+
# element's name. It's still name='...' in XML.
|
|
49
|
+
#
|
|
50
|
+
# This is all a bit analoguous to Jabber::RosterItem, used by
|
|
51
|
+
# Jabber::IqQueryRoster. But this class lacks the subscription and
|
|
52
|
+
# ask attributes.
|
|
53
|
+
class XRosterItem < REXML::Element
|
|
54
|
+
##
|
|
55
|
+
# Construct a new roster item
|
|
56
|
+
# jid:: [JID] Jabber ID
|
|
57
|
+
# iname:: [String] Name in the roster
|
|
58
|
+
def initialize(jid=nil, iname=nil)
|
|
59
|
+
super('item')
|
|
60
|
+
self.jid = jid
|
|
61
|
+
self.iname = iname
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Create new XRosterItem from REXML::Element
|
|
66
|
+
# item:: [REXML::Element] source element to copy attributes and children from
|
|
67
|
+
def XRosterItem.import(item)
|
|
68
|
+
XRosterItem::new.import(item)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Get name of roster item
|
|
73
|
+
#
|
|
74
|
+
# names can be set by the roster's owner himself
|
|
75
|
+
# return:: [String]
|
|
76
|
+
def iname
|
|
77
|
+
attributes['name']
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Set name of roster item
|
|
82
|
+
# val:: [String] Name for this item
|
|
83
|
+
def iname=(val)
|
|
84
|
+
attributes['name'] = val
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Get JID of roster item
|
|
89
|
+
# Resource of the JID will _not_ be stripped
|
|
90
|
+
# return:: [JID]
|
|
91
|
+
def jid
|
|
92
|
+
JID::new(attributes['jid'])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# Set JID of roster item
|
|
97
|
+
# val:: [JID] or nil
|
|
98
|
+
def jid=(val)
|
|
99
|
+
attributes['jid'] = val.nil? ? nil : val.to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# Get action for this roster item
|
|
104
|
+
# * :add
|
|
105
|
+
# * :modify
|
|
106
|
+
# * :delete
|
|
107
|
+
# result:: [Symbol] (defaults to :add according to JEP-0144)
|
|
108
|
+
def action
|
|
109
|
+
case attributes['action']
|
|
110
|
+
when 'modify' then :modify
|
|
111
|
+
when 'delete' then :delete
|
|
112
|
+
else :add
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Set action for this roster item
|
|
118
|
+
# (see action)
|
|
119
|
+
def action=(a)
|
|
120
|
+
case a
|
|
121
|
+
when :modify then attributes['action'] = 'modify'
|
|
122
|
+
when :delete then attributes['action'] = 'delete'
|
|
123
|
+
else attributes['action'] = 'add'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
##
|
|
128
|
+
# Get groups the item belongs to
|
|
129
|
+
# result:: [Array] of [String] The groups
|
|
130
|
+
def groups
|
|
131
|
+
result = []
|
|
132
|
+
each_element('group') { |group|
|
|
133
|
+
result.push(group.text)
|
|
134
|
+
}
|
|
135
|
+
result
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# Set groups the item belongs to,
|
|
140
|
+
# deletes old groups first.
|
|
141
|
+
#
|
|
142
|
+
# See JEP 0083 for nested groups
|
|
143
|
+
# ary:: [Array] New groups, duplicate values will be removed
|
|
144
|
+
def groups=(ary)
|
|
145
|
+
# Delete old group elements
|
|
146
|
+
delete_elements('group')
|
|
147
|
+
|
|
148
|
+
# Add new group elements
|
|
149
|
+
ary.uniq.each { |group|
|
|
150
|
+
add_element('group').text = group
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
end #Class XRosterItem
|
|
154
|
+
end #Module Roster
|
|
155
|
+
end #Module Jabber
|
data/lib/xmpp4r/sasl.rb
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'digest/md5'
|
|
3
|
+
|
|
4
|
+
module Jabber
|
|
5
|
+
##
|
|
6
|
+
# Helpers for SASL authentication (RFC2222)
|
|
7
|
+
#
|
|
8
|
+
# You might not need to use them directly, they are
|
|
9
|
+
# invoked by Jabber::Client#auth
|
|
10
|
+
module SASL
|
|
11
|
+
NS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Factory function to obtain a SASL helper for the specified mechanism
|
|
15
|
+
def SASL::new(stream, mechanism)
|
|
16
|
+
case mechanism
|
|
17
|
+
when 'DIGEST-MD5'
|
|
18
|
+
DigestMD5.new(stream)
|
|
19
|
+
when 'PLAIN'
|
|
20
|
+
Plain.new(stream)
|
|
21
|
+
else
|
|
22
|
+
raise "Unknown SASL mechanism: #{mechanism}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# SASL mechanism base class (stub)
|
|
28
|
+
class Base
|
|
29
|
+
def initialize(stream)
|
|
30
|
+
@stream = stream
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def generate_auth(mechanism, text=nil)
|
|
36
|
+
auth = REXML::Element.new 'auth'
|
|
37
|
+
auth.add_namespace NS_SASL
|
|
38
|
+
auth.attributes['mechanism'] = mechanism
|
|
39
|
+
auth.text = text
|
|
40
|
+
auth
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def generate_nonce
|
|
44
|
+
Digest::MD5.new(Time.new.to_f.to_s).hexdigest
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# SASL PLAIN authentication helper (RFC2595)
|
|
50
|
+
class Plain < Base
|
|
51
|
+
##
|
|
52
|
+
# Authenticate via sending password in clear-text
|
|
53
|
+
def auth(password)
|
|
54
|
+
auth_text = "#{@stream.jid.strip}\x00#{@stream.jid.node}\x00#{password}"
|
|
55
|
+
error = nil
|
|
56
|
+
@stream.send(generate_auth('PLAIN', Base64::encode64(auth_text).strip)) { |reply|
|
|
57
|
+
if reply.name != 'success'
|
|
58
|
+
error = reply.first_element(nil).name
|
|
59
|
+
end
|
|
60
|
+
true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
raise error if error
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# SASL DIGEST-MD5 authentication helper (RFC2831)
|
|
69
|
+
class DigestMD5 < Base
|
|
70
|
+
##
|
|
71
|
+
# Sends the wished auth mechanism and wait for a challenge
|
|
72
|
+
#
|
|
73
|
+
# (proceed with DigestMD5#auth)
|
|
74
|
+
def initialize(stream)
|
|
75
|
+
super
|
|
76
|
+
|
|
77
|
+
challenge = {}
|
|
78
|
+
error = nil
|
|
79
|
+
@stream.send(generate_auth('DIGEST-MD5')) { |reply|
|
|
80
|
+
if reply.name == 'challenge' and reply.namespace == NS_SASL
|
|
81
|
+
challenge_text = Base64::decode64(reply.text)
|
|
82
|
+
challenge_text.split(/,/).each { |s|
|
|
83
|
+
key, value = s.split(/=/, 2)
|
|
84
|
+
value.sub!(/^"/, '')
|
|
85
|
+
value.sub!(/"$/, '')
|
|
86
|
+
challenge[key] = value
|
|
87
|
+
}
|
|
88
|
+
else
|
|
89
|
+
error = reply.first_element(nil).name
|
|
90
|
+
end
|
|
91
|
+
true
|
|
92
|
+
}
|
|
93
|
+
raise error if error
|
|
94
|
+
|
|
95
|
+
@nonce = challenge['nonce']
|
|
96
|
+
@realm = challenge['realm']
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# * Send a response
|
|
101
|
+
# * Wait for the server's challenge (which aren't checked)
|
|
102
|
+
# * Send a blind response to the server's challenge
|
|
103
|
+
def auth(password)
|
|
104
|
+
response = {}
|
|
105
|
+
response['nonce'] = @nonce
|
|
106
|
+
response['charset'] = 'utf-8'
|
|
107
|
+
response['username'] = @stream.jid.node
|
|
108
|
+
response['realm'] = @realm || @stream.jid.domain
|
|
109
|
+
response['cnonce'] = generate_nonce
|
|
110
|
+
response['nc'] = '00000001'
|
|
111
|
+
response['qop'] = 'auth'
|
|
112
|
+
response['digest-uri'] = "xmpp/#{@stream.jid.domain}"
|
|
113
|
+
response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop'])
|
|
114
|
+
response.each { |key,value|
|
|
115
|
+
unless %w(nc qop response charset).include? key
|
|
116
|
+
response[key] = "\"#{value}\""
|
|
117
|
+
end
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
r = REXML::Element.new('response')
|
|
121
|
+
r.add_namespace NS_SASL
|
|
122
|
+
r.text = Base64::encode64(response.collect { |k,v| "#{k}=#{v}" }.join(',')).gsub(/\s/, '')
|
|
123
|
+
error = nil
|
|
124
|
+
@stream.send(r) { |reply|
|
|
125
|
+
if reply.name != 'challenge'
|
|
126
|
+
error = reply.first_element(nil).name
|
|
127
|
+
end
|
|
128
|
+
true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
raise error if error
|
|
132
|
+
|
|
133
|
+
# TODO: check the challenge from the server
|
|
134
|
+
|
|
135
|
+
r.text = nil
|
|
136
|
+
@stream.send(r) { |reply|
|
|
137
|
+
if reply.name != 'success'
|
|
138
|
+
error = reply.first_element(nil).name
|
|
139
|
+
end
|
|
140
|
+
true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
raise error if error
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
##
|
|
149
|
+
# Function from RFC2831
|
|
150
|
+
def h(s); Digest::MD5.digest(s); end
|
|
151
|
+
##
|
|
152
|
+
# Function from RFC2831
|
|
153
|
+
def hh(s); Digest::MD5.hexdigest(s); end
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# Calculate the value for the response field
|
|
157
|
+
def response_value(username, realm, digest_uri, passwd, nonce, cnonce, qop)
|
|
158
|
+
a1_h = h("#{username}:#{realm}:#{passwd}")
|
|
159
|
+
a1 = "#{a1_h}:#{nonce}:#{cnonce}"
|
|
160
|
+
#a2 = "AUTHENTICATE:#{digest_uri}#{(qop == 'auth') ? '' : ':00000000000000000000000000000000'}"
|
|
161
|
+
a2 = "AUTHENTICATE:#{digest_uri}"
|
|
162
|
+
|
|
163
|
+
hh("#{hh(a1)}:#{nonce}:00000001:#{cnonce}:#{qop}:#{hh(a2)}")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|