vinesmod 0.4.5
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/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +43 -0
- data/Rakefile +57 -0
- data/bin/vines +93 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3366 -0
- data/conf/config.rb +149 -0
- data/lib/vines.rb +197 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/register.rb +27 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/command/unregister.rb +27 -0
- data/lib/vines/config.rb +213 -0
- data/lib/vines/config/host.rb +119 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +35 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/register.rb +42 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/message.rb +43 -0
- data/lib/vines/stanza/presence.rb +156 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/storage.rb +188 -0
- data/lib/vines/storage/local.rb +165 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +260 -0
- data/lib/vines/store.rb +94 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/parser.rb +78 -0
- data/lib/vines/stream/sasl.rb +92 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +43 -0
- data/vines.gemspec +36 -0
- data/web/404.html +51 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +362 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/index.html +16 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/stylesheets/chat.css +144 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/button.coffee +25 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/filter.coffee +49 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +68 -0
- data/web/lib/coffeescripts/logout.coffee +5 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/notification.coffee +14 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +229 -0
- data/web/lib/coffeescripts/transfer.coffee +106 -0
- data/web/lib/images/dark-gray.png +0 -0
- data/web/lib/images/default-user.png +0 -0
- data/web/lib/images/light-gray.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/images/white.png +0 -0
- data/web/lib/javascripts/base.js +12 -0
- data/web/lib/javascripts/icons.js +110 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +4 -0
- data/web/lib/javascripts/raphael.js +6 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +385 -0
- data/web/lib/stylesheets/login.css +68 -0
- metadata +423 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq < Stanza
|
6
|
+
register "/iq"
|
7
|
+
|
8
|
+
VALID_TYPES = %w[get set result error].freeze
|
9
|
+
|
10
|
+
VALID_TYPES.each do |type|
|
11
|
+
define_method "#{type}?" do
|
12
|
+
self['type'] == type
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def process
|
17
|
+
if self['id'] && VALID_TYPES.include?(self['type'])
|
18
|
+
route_iq or raise StanzaErrors::FeatureNotImplemented.new(@node, 'cancel')
|
19
|
+
else
|
20
|
+
raise StanzaErrors::BadRequest.new(@node, 'modify')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_result
|
25
|
+
doc = Document.new
|
26
|
+
doc.create_element('iq',
|
27
|
+
'from' => validate_to || stream.domain,
|
28
|
+
'id' => self['id'],
|
29
|
+
'to' => stream.user.jid,
|
30
|
+
'type' => 'result')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Return false if this IQ stanza is addressed to the server, or a pubsub
|
36
|
+
# service hosted here, and must be handled locally. Return true if the
|
37
|
+
# stanza must not be handled locally and has been routed to the appropriate
|
38
|
+
# component, s2s, or c2s stream.
|
39
|
+
def route_iq
|
40
|
+
to = validate_to
|
41
|
+
return false if to.nil? || stream.config.vhost?(to) || to_pubsub_domain?
|
42
|
+
self['from'] = stream.user.jid.to_s
|
43
|
+
local? ? broadcast(stream.connected_resources(to)) : route
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
class Auth < Query
|
7
|
+
register "/iq[@id and @type='get']/ns:query", 'ns' => NAMESPACES[:non_sasl]
|
8
|
+
|
9
|
+
def process
|
10
|
+
# XEP-0078 says we MUST send a service-unavailable error
|
11
|
+
# here, but Adium 1.4.1 won't login if we do that, so just
|
12
|
+
# swallow this stanza.
|
13
|
+
# raise StanzaErrors::ServiceUnavailable.new(@node, 'cancel')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
class DiscoInfo < Query
|
7
|
+
NS = NAMESPACES[:disco_info]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='get']/ns:query", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
result = to_result.tap do |el|
|
14
|
+
el << el.document.create_element('query') do |query|
|
15
|
+
query.default_namespace = NS
|
16
|
+
if to_pubsub_domain?
|
17
|
+
identity(query, 'pubsub', 'service')
|
18
|
+
pubsub = [:pubsub_create, :pubsub_delete, :pubsub_instant, :pubsub_item_ids, :pubsub_publish, :pubsub_subscribe]
|
19
|
+
features(query, :disco_info, :ping, :pubsub, *pubsub)
|
20
|
+
else
|
21
|
+
identity(query, 'server', 'im')
|
22
|
+
features = [:disco_info, :disco_items, :ping, :vcard, :version]
|
23
|
+
features << :storage if stream.config.private_storage?(validate_to || stream.domain)
|
24
|
+
features(query, features)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
stream.write(result)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def identity(query, category, type)
|
34
|
+
query << query.document.create_element('identity', 'category' => category, 'type' => type)
|
35
|
+
end
|
36
|
+
|
37
|
+
def features(query, *features)
|
38
|
+
features.flatten.each do |feature|
|
39
|
+
query << query.document.create_element('feature', 'var' => NAMESPACES[feature])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
class DiscoItems < Query
|
7
|
+
NS = NAMESPACES[:disco_items]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='get']/ns:query", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
return if route_iq || !allowed?
|
13
|
+
result = to_result.tap do |el|
|
14
|
+
el << el.document.create_element('query') do |query|
|
15
|
+
query.default_namespace = NS
|
16
|
+
unless to_pubsub_domain?
|
17
|
+
to = (validate_to || stream.domain).to_s
|
18
|
+
stream.config.vhost(to).disco_items.each do |domain|
|
19
|
+
query << el.document.create_element('item', 'jid' => domain)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
stream.write(result)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
class Ping < Iq
|
7
|
+
register "/iq[@id and @type='get']/ns:ping", 'ns' => NAMESPACES[:ping]
|
8
|
+
|
9
|
+
def process
|
10
|
+
return if route_iq || !allowed?
|
11
|
+
stream.write(to_result)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
# Implements the Private Storage feature defined in XEP-0049. Clients are
|
7
|
+
# allowed to save arbitrary XML documents on the server, identified by
|
8
|
+
# element name and namespace.
|
9
|
+
class PrivateStorage < Query
|
10
|
+
NS = NAMESPACES[:storage]
|
11
|
+
|
12
|
+
register "/iq[@id and (@type='get' or @type='set')]/ns:query", 'ns' => NS
|
13
|
+
|
14
|
+
def process
|
15
|
+
validate_to_address
|
16
|
+
validate_storage_enabled
|
17
|
+
validate_children_size
|
18
|
+
validate_namespaces
|
19
|
+
get? ? retrieve_fragment : update_fragment
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def retrieve_fragment
|
25
|
+
found = storage.find_fragment(stream.user.jid, elements.first.elements.first)
|
26
|
+
raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless found
|
27
|
+
|
28
|
+
result = to_result do |node|
|
29
|
+
node << node.document.create_element('query') do |query|
|
30
|
+
query.default_namespace = NS
|
31
|
+
query << found
|
32
|
+
end
|
33
|
+
end
|
34
|
+
stream.write(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_fragment
|
38
|
+
elements.first.elements.each do |node|
|
39
|
+
storage.save_fragment(stream.user.jid, node)
|
40
|
+
end
|
41
|
+
stream.write(to_result)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def to_result
|
47
|
+
super.tap do |node|
|
48
|
+
node['from'] = stream.user.jid.to_s
|
49
|
+
yield node if block_given?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_children_size
|
54
|
+
size = elements.first.elements.size
|
55
|
+
if (get? && size != 1) || (set? && size == 0)
|
56
|
+
raise StanzaErrors::NotAcceptable.new(self, 'modify')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_to_address
|
61
|
+
to = validate_to
|
62
|
+
unless to.nil? || to == stream.user.jid.bare
|
63
|
+
raise StanzaErrors::Forbidden.new(self, 'cancel')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_storage_enabled
|
68
|
+
unless stream.config.private_storage?(stream.domain)
|
69
|
+
raise StanzaErrors::ServiceUnavailable.new(self, 'cancel')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_namespaces
|
74
|
+
elements.first.elements.each do |node|
|
75
|
+
if node.namespace.nil? || NAMESPACES.values.include?(node.namespace.href)
|
76
|
+
raise StanzaErrors::NotAcceptable.new(self, 'modify')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
class Register < Query
|
7
|
+
NS = NAMESPACES[:register]
|
8
|
+
|
9
|
+
register "/iq[@id and @type='set']/ns:query", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
if is_stream_owner
|
13
|
+
current_user = storage(stream.domain).find_user(stream.user.jid)
|
14
|
+
password = @node.xpath("//iq/jir:query//jir:password", {"jir"=>"jabber:iq:register"}).text
|
15
|
+
unless password.nil?
|
16
|
+
current_user.password = BCrypt::Password.create(password.to_s)
|
17
|
+
storage.save_user(current_user)
|
18
|
+
stream.write(to_result)
|
19
|
+
else
|
20
|
+
raise StanzaErrors::NotAcceptable.new(self, 'cancel')
|
21
|
+
end
|
22
|
+
else
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def is_stream_owner
|
30
|
+
stream.user.jid.bare == jid_from_username.bare
|
31
|
+
end
|
32
|
+
|
33
|
+
def jid_from_username
|
34
|
+
username = @node.xpath("//iq/jir:query//jir:username", {"jir"=>"jabber:iq:register"}).text
|
35
|
+
dom = @node.attributes["to"].text
|
36
|
+
JID.new(username, dom)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stanza
|
5
|
+
class Iq
|
6
|
+
class Roster < Query
|
7
|
+
NS = NAMESPACES[:roster]
|
8
|
+
|
9
|
+
register "/iq[@id and (@type='get' or @type='set')]/ns:query", 'ns' => NS
|
10
|
+
|
11
|
+
def process
|
12
|
+
validate_to_address
|
13
|
+
get? ? roster_query : update_roster
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Send an iq result stanza containing roster items to the user in
|
19
|
+
# response to their roster get request. Requesting the roster makes
|
20
|
+
# this stream an "interested resource" that can now receive roster
|
21
|
+
# updates.
|
22
|
+
def roster_query
|
23
|
+
stream.requested_roster!
|
24
|
+
stream.write(stream.user.to_roster_xml(self['id']))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Roster sets must have no 'to' address or be addressed to the same
|
28
|
+
# JID that sent the stanza. RFC 6121 sections 2.1.5 and 2.3.3.
|
29
|
+
def validate_to_address
|
30
|
+
to = validate_to
|
31
|
+
unless to.nil? || to.bare == stream.user.jid.bare
|
32
|
+
raise StanzaErrors::Forbidden.new(self, 'auth')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add, update, or delete the roster item contained in the iq set
|
37
|
+
# stanza received from the client. RFC 6121 sections 2.3, 2.4, 2.5.
|
38
|
+
def update_roster
|
39
|
+
items = self.xpath('ns:query/ns:item', 'ns' => NS)
|
40
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if items.size != 1
|
41
|
+
item = items.first
|
42
|
+
|
43
|
+
jid = JID.new(item['jid']) rescue (raise StanzaErrors::JidMalformed.new(self, 'modify'))
|
44
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if jid.empty? || !jid.bare?
|
45
|
+
|
46
|
+
if item['subscription'] == 'remove'
|
47
|
+
remove_contact(jid)
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
raise StanzaErrors::NotAllowed.new(self, 'modify') if jid == stream.user.jid.bare
|
52
|
+
groups = item.xpath('ns:group', 'ns' => NS).map {|g| g.text.strip }
|
53
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if groups.uniq!
|
54
|
+
raise StanzaErrors::NotAcceptable.new(self, 'modify') if groups.include?('')
|
55
|
+
|
56
|
+
contact = stream.user.contact(jid)
|
57
|
+
unless contact
|
58
|
+
contact = Contact.new(jid: jid)
|
59
|
+
stream.user.roster << contact
|
60
|
+
end
|
61
|
+
contact.name = item['name']
|
62
|
+
contact.groups = groups
|
63
|
+
storage.save_user(stream.user)
|
64
|
+
stream.update_user_streams(stream.user)
|
65
|
+
send_result_iq
|
66
|
+
push_roster_updates(stream.user.jid, contact)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Remove the contact with this JID from the user's roster and send
|
70
|
+
# roster pushes to the user's interested resources. This is triggered
|
71
|
+
# by receiving an iq set with an item element like
|
72
|
+
# <item jid="alice@wonderland.lit" subscription="remove"/>. RFC 6121
|
73
|
+
# section 2.5.
|
74
|
+
def remove_contact(jid)
|
75
|
+
contact = stream.user.contact(jid)
|
76
|
+
raise StanzaErrors::ItemNotFound.new(self, 'modify') unless contact
|
77
|
+
if local_jid?(contact.jid)
|
78
|
+
user = storage(contact.jid.domain).find_user(contact.jid)
|
79
|
+
end
|
80
|
+
|
81
|
+
if user && user.contact(stream.user.jid)
|
82
|
+
user.contact(stream.user.jid).subscription = 'none'
|
83
|
+
user.contact(stream.user.jid).ask = nil
|
84
|
+
end
|
85
|
+
stream.user.remove_contact(contact.jid)
|
86
|
+
[user, stream.user].compact.each do |save|
|
87
|
+
storage(save.jid.domain).save_user(save)
|
88
|
+
stream.update_user_streams(save)
|
89
|
+
end
|
90
|
+
|
91
|
+
send_result_iq
|
92
|
+
push_roster_updates(stream.user.jid,
|
93
|
+
Contact.new(jid: contact.jid, subscription: 'remove'))
|
94
|
+
|
95
|
+
if local_jid?(contact.jid)
|
96
|
+
send_unavailable(stream.user.jid, contact.jid) if contact.subscribed_from?
|
97
|
+
send_unsubscribe(contact)
|
98
|
+
if user && user.contact(stream.user.jid)
|
99
|
+
push_roster_updates(contact.jid, user.contact(stream.user.jid))
|
100
|
+
end
|
101
|
+
else
|
102
|
+
send_unsubscribe(contact)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Notify the contact that it's been removed from the user's roster
|
107
|
+
# and no longer has any presence relationship with the user.
|
108
|
+
def send_unsubscribe(contact)
|
109
|
+
presence = [%w[to unsubscribe], %w[from unsubscribed]].map do |meth, type|
|
110
|
+
presence(contact.jid, type) if contact.send("subscribed_#{meth}?")
|
111
|
+
end.compact
|
112
|
+
broadcast_to_interested_resources(presence, contact.jid)
|
113
|
+
end
|
114
|
+
|
115
|
+
def presence(to, type)
|
116
|
+
doc = Document.new
|
117
|
+
doc.create_element('presence',
|
118
|
+
'from' => stream.user.jid.bare.to_s,
|
119
|
+
'id' => Kit.uuid,
|
120
|
+
'to' => to.to_s,
|
121
|
+
'type' => type)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Send an iq set stanza to the user's interested resources, letting them
|
125
|
+
# know their roster has been updated.
|
126
|
+
def push_roster_updates(to, contact)
|
127
|
+
stream.interested_resources(to).each do |recipient|
|
128
|
+
contact.send_roster_push(recipient)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def send_result_iq
|
133
|
+
node = to_result
|
134
|
+
node.remove_attribute('from')
|
135
|
+
stream.write(node)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|