vinesmod 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|