vines 0.1.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/LICENSE +19 -0
- data/README +34 -0
- data/Rakefile +55 -0
- data/bin/vines +95 -0
- data/conf/certs/README +32 -0
- data/conf/certs/ca-bundle.crt +3987 -0
- data/conf/config.rb +114 -0
- data/lib/vines.rb +155 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +49 -0
- data/lib/vines/command/init.rb +58 -0
- data/lib/vines/command/ldap.rb +35 -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/config.rb +191 -0
- data/lib/vines/contact.rb +99 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +56 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/router.rb +125 -0
- data/lib/vines/stanza.rb +55 -0
- data/lib/vines/stanza/iq.rb +50 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +25 -0
- data/lib/vines/stanza/iq/disco_items.rb +23 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +153 -0
- data/lib/vines/stanza/iq/session.rb +22 -0
- data/lib/vines/stanza/iq/vcard.rb +58 -0
- data/lib/vines/stanza/message.rb +41 -0
- data/lib/vines/stanza/presence.rb +119 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +38 -0
- data/lib/vines/stanza/presence/subscribe.rb +66 -0
- data/lib/vines/stanza/presence/subscribed.rb +64 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +57 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +50 -0
- data/lib/vines/storage.rb +216 -0
- data/lib/vines/storage/couchdb.rb +119 -0
- data/lib/vines/storage/ldap.rb +59 -0
- data/lib/vines/storage/local.rb +66 -0
- data/lib/vines/storage/redis.rb +108 -0
- data/lib/vines/storage/sql.rb +174 -0
- data/lib/vines/store.rb +51 -0
- data/lib/vines/stream.rb +198 -0
- data/lib/vines/stream/client.rb +131 -0
- data/lib/vines/stream/client/auth.rb +94 -0
- data/lib/vines/stream/client/auth_restart.rb +33 -0
- data/lib/vines/stream/client/bind.rb +58 -0
- data/lib/vines/stream/client/bind_restart.rb +25 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +15 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +37 -0
- data/lib/vines/stream/component.rb +53 -0
- data/lib/vines/stream/component/handshake.rb +25 -0
- data/lib/vines/stream/component/ready.rb +24 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/http.rb +111 -0
- data/lib/vines/stream/http/http_request.rb +22 -0
- data/lib/vines/stream/http/http_state.rb +139 -0
- data/lib/vines/stream/http/http_states.rb +53 -0
- data/lib/vines/stream/parser.rb +78 -0
- data/lib/vines/stream/server.rb +126 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +19 -0
- data/lib/vines/stream/server/final_restart.rb +20 -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 +28 -0
- data/lib/vines/stream/server/outbound/final_features.rb +27 -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 +31 -0
- data/lib/vines/stream/server/ready.rb +20 -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 +55 -0
- data/lib/vines/token_bucket.rb +46 -0
- data/lib/vines/user.rb +124 -0
- data/lib/vines/version.rb +5 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/test/config_test.rb +396 -0
- data/test/error_test.rb +59 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +71 -0
- data/test/kit_test.rb +21 -0
- data/test/router_test.rb +60 -0
- data/test/stanza/iq/roster_test.rb +198 -0
- data/test/stanza/iq/session_test.rb +30 -0
- data/test/stanza/iq/vcard_test.rb +159 -0
- data/test/stanza/message_test.rb +124 -0
- data/test/stanza/presence/subscribe_test.rb +75 -0
- data/test/storage/couchdb_test.rb +102 -0
- data/test/storage/ldap_test.rb +207 -0
- data/test/storage/local_test.rb +54 -0
- data/test/storage/redis_test.rb +75 -0
- data/test/storage/sql_test.rb +55 -0
- data/test/storage/storage_tests.rb +134 -0
- data/test/storage_test.rb +90 -0
- data/test/stream/client/auth_test.rb +127 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/component/handshake_test.rb +46 -0
- data/test/stream/component/ready_test.rb +105 -0
- data/test/stream/component/start_test.rb +41 -0
- data/test/stream/parser_test.rb +121 -0
- data/test/stream/server/outbound/auth_test.rb +77 -0
- data/test/stream/server/ready_test.rb +100 -0
- data/test/token_bucket_test.rb +24 -0
- data/test/user_test.rb +64 -0
- metadata +318 -0
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
get? ? roster_query : update_roster
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Send an iq result stanza containing roster items to the user in
|
|
18
|
+
# response to their roster get request. Requesting the roster makes
|
|
19
|
+
# this stream an "interested resource" that can now receive roster
|
|
20
|
+
# updates.
|
|
21
|
+
def roster_query
|
|
22
|
+
stream.requested_roster!
|
|
23
|
+
stream.write(stream.user.to_roster_xml(self['id']))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Roster sets must have no 'to' address or be addressed to the same
|
|
27
|
+
# JID that sent the stanza. RFC 6121 sections 2.1.5 and 2.3.3.
|
|
28
|
+
def validate_to_address
|
|
29
|
+
to = (self['to'] || '').strip
|
|
30
|
+
unless to.empty? || JID.new(to).bare == stream.user.jid.bare
|
|
31
|
+
raise StanzaErrors::Forbidden.new(self, 'auth')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Add, update, or delete the roster item contained in the iq set
|
|
36
|
+
# stanza received from the client. RFC 6121 sections 2.3, 2.4, 2.5.
|
|
37
|
+
def update_roster
|
|
38
|
+
validate_to_address
|
|
39
|
+
|
|
40
|
+
items = self.xpath('ns:query/ns:item', 'ns' => NS)
|
|
41
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if items.size != 1
|
|
42
|
+
item = items.first
|
|
43
|
+
|
|
44
|
+
jid = (item['jid'] || '').strip.empty? ? nil : JID.new(item['jid'].strip)
|
|
45
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') unless jid && jid.bare?
|
|
46
|
+
|
|
47
|
+
if item['subscription'] == 'remove'
|
|
48
|
+
remove_contact(jid)
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
raise StanzaErrors::NotAllowed.new(self, 'modify') if jid == stream.user.jid.bare
|
|
53
|
+
groups = item.xpath('ns:group', 'ns' => NS).map {|g| g.text.strip }
|
|
54
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if groups.uniq!
|
|
55
|
+
raise StanzaErrors::NotAcceptable.new(self, 'modify') if groups.include?('')
|
|
56
|
+
|
|
57
|
+
contact = stream.user.contact(jid)
|
|
58
|
+
unless contact
|
|
59
|
+
contact = Contact.new(:jid => jid)
|
|
60
|
+
stream.user.roster << contact
|
|
61
|
+
end
|
|
62
|
+
contact.name = item['name']
|
|
63
|
+
contact.groups = groups
|
|
64
|
+
storage.save_user(stream.user)
|
|
65
|
+
stream.update_user_streams(stream.user)
|
|
66
|
+
send_result_iq
|
|
67
|
+
push_roster_updates(stream.user.jid, contact)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Remove the contact with this JID from the user's roster and send
|
|
71
|
+
# roster pushes to the user's interested resources. This is triggered
|
|
72
|
+
# by receiving an iq set with an item element like
|
|
73
|
+
# <item jid="alice@wonderland.lit" subscription="remove"/>. RFC 6121
|
|
74
|
+
# section 2.5.
|
|
75
|
+
def remove_contact(jid)
|
|
76
|
+
contact = stream.user.contact(jid)
|
|
77
|
+
raise StanzaErrors::ItemNotFound.new(self, 'modify') unless contact
|
|
78
|
+
if router.local_jid?(contact.jid)
|
|
79
|
+
user = storage(contact.jid.domain).find_user(contact.jid)
|
|
80
|
+
remove(contact, user)
|
|
81
|
+
else
|
|
82
|
+
remove(contact, nil)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def remove(contact, user=nil)
|
|
87
|
+
if user && user.contact(stream.user.jid)
|
|
88
|
+
user.contact(stream.user.jid).subscription = 'none'
|
|
89
|
+
user.contact(stream.user.jid).ask = nil
|
|
90
|
+
end
|
|
91
|
+
stream.user.remove_contact(contact.jid)
|
|
92
|
+
[user, stream.user].compact.each do |save|
|
|
93
|
+
storage(save.jid.domain).save_user(save)
|
|
94
|
+
stream.update_user_streams(save)
|
|
95
|
+
end
|
|
96
|
+
send_result_iq
|
|
97
|
+
push_roster_updates(stream.user.jid, Contact.new(
|
|
98
|
+
:jid => contact.jid,
|
|
99
|
+
:subscription => 'remove'))
|
|
100
|
+
|
|
101
|
+
presence = [%w[to unsubscribe], %w[from unsubscribed]].map do |meth, type|
|
|
102
|
+
if contact.send("subscribed_#{meth}?")
|
|
103
|
+
doc = Document.new
|
|
104
|
+
doc.create_element('presence',
|
|
105
|
+
'from' => stream.user.jid.bare.to_s,
|
|
106
|
+
'to' => contact.jid.to_s,
|
|
107
|
+
'type' => type)
|
|
108
|
+
end
|
|
109
|
+
end.compact
|
|
110
|
+
|
|
111
|
+
if router.local_jid?(contact.jid)
|
|
112
|
+
router.interested_resources(contact.jid).each do |recipient|
|
|
113
|
+
presence.each {|el| recipient.write(el) }
|
|
114
|
+
doc = Document.new
|
|
115
|
+
el = doc.create_element('presence',
|
|
116
|
+
'from' => stream.user.jid.bare.to_s,
|
|
117
|
+
'to' => recipient.user.jid.to_s,
|
|
118
|
+
'type' => 'unavailable')
|
|
119
|
+
recipient.write(el)
|
|
120
|
+
end
|
|
121
|
+
push_roster_updates(contact.jid, Contact.new(
|
|
122
|
+
:jid => stream.user.jid,
|
|
123
|
+
:subscription => 'none'))
|
|
124
|
+
else
|
|
125
|
+
presence.each {|el| router.route(el) }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Send an iq set stanza to the user's interested resources, letting them
|
|
130
|
+
# know their roster has been updated.
|
|
131
|
+
def push_roster_updates(to, contact)
|
|
132
|
+
doc = Document.new
|
|
133
|
+
el = doc.create_element('iq', 'type' => 'set') do |node|
|
|
134
|
+
node << doc.create_element('query', 'xmlns' => NAMESPACES[:roster]) do |query|
|
|
135
|
+
query << contact.to_roster_xml
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
router.interested_resources(to).each do |recipient|
|
|
139
|
+
el['id'] = Kit.uuid
|
|
140
|
+
el['to'] = recipient.user.jid.to_s
|
|
141
|
+
recipient.write(el)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def send_result_iq
|
|
146
|
+
doc = Document.new
|
|
147
|
+
node = doc.create_element('iq', 'id' => self['id'], 'type' => 'result')
|
|
148
|
+
stream.write(node)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Iq
|
|
6
|
+
# Session support is deprecated, but Adium requires it so reply with an
|
|
7
|
+
# iq result stanza.
|
|
8
|
+
class Session < Iq
|
|
9
|
+
register "/iq[@id and @type='set']/ns:session", 'ns' => NAMESPACES[:session]
|
|
10
|
+
|
|
11
|
+
def process
|
|
12
|
+
doc = Document.new
|
|
13
|
+
result = doc.create_element('iq',
|
|
14
|
+
'from' => stream.domain,
|
|
15
|
+
'id' => self['id'],
|
|
16
|
+
'type' => 'result')
|
|
17
|
+
stream.write(result)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Iq
|
|
6
|
+
class Vcard < Iq
|
|
7
|
+
NS = NAMESPACES[:vcard]
|
|
8
|
+
|
|
9
|
+
register "/iq[@id and @type='get' or @type='set']/ns:vCard", 'ns' => NS
|
|
10
|
+
|
|
11
|
+
def process
|
|
12
|
+
if local?
|
|
13
|
+
get? ? vcard_query : vcard_update
|
|
14
|
+
else
|
|
15
|
+
self['from'] = stream.user.jid.to_s
|
|
16
|
+
route
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def vcard_query
|
|
23
|
+
jid = (self['to'] || '').strip
|
|
24
|
+
jid = jid.empty? ? stream.user.jid.bare : JID.new(jid).bare
|
|
25
|
+
card = storage.find_vcard(jid)
|
|
26
|
+
|
|
27
|
+
raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless card
|
|
28
|
+
|
|
29
|
+
doc = Document.new
|
|
30
|
+
result = doc.create_element('iq') do |node|
|
|
31
|
+
node['from'] = jid.to_s unless jid == stream.user.jid.bare
|
|
32
|
+
node['id'] = self['id']
|
|
33
|
+
node['to'] = stream.user.jid.to_s
|
|
34
|
+
node['type'] = 'result'
|
|
35
|
+
node << card
|
|
36
|
+
end
|
|
37
|
+
stream.write(result)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def vcard_update
|
|
41
|
+
to = (self['to'] || '').strip
|
|
42
|
+
unless to.empty? || to == stream.user.jid.bare.to_s
|
|
43
|
+
raise StanzaErrors::Forbidden.new(self, 'auth')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
storage.save_vcard(stream.user.jid, elements.first)
|
|
47
|
+
|
|
48
|
+
doc = Document.new
|
|
49
|
+
result = doc.create_element('iq',
|
|
50
|
+
'id' => self['id'],
|
|
51
|
+
'to' => stream.user.jid.to_s,
|
|
52
|
+
'type' => 'result')
|
|
53
|
+
stream.write(result)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Message < Stanza
|
|
6
|
+
register "/message"
|
|
7
|
+
|
|
8
|
+
TYPE, TO, FROM = %w[type to from].map {|s| s.freeze }
|
|
9
|
+
VALID_TYPES = %w[chat error groupchat headline normal].freeze
|
|
10
|
+
|
|
11
|
+
VALID_TYPES.each do |type|
|
|
12
|
+
define_method "#{type}?" do
|
|
13
|
+
self[TYPE] == type
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process
|
|
18
|
+
unless self[TYPE].nil? || VALID_TYPES.include?(self[TYPE])
|
|
19
|
+
raise StanzaErrors::BadRequest.new(self, 'modify')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if local?
|
|
23
|
+
to = (self[TO] || '').strip
|
|
24
|
+
to = to.empty? ? stream.user.jid.bare : JID.new(to)
|
|
25
|
+
recipients = router.connected_resources(to)
|
|
26
|
+
if recipients.empty?
|
|
27
|
+
if user = storage(to.domain).find_user(to)
|
|
28
|
+
# TODO Implement offline messaging storage
|
|
29
|
+
raise StanzaErrors::ServiceUnavailable.new(self, 'cancel')
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
broadcast(recipients)
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
self[FROM] = stream.user.jid.to_s
|
|
36
|
+
route
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Presence < Stanza
|
|
6
|
+
register "/presence"
|
|
7
|
+
|
|
8
|
+
VALID_TYPES = %w[subscribe subscribed unsubscribe unsubscribed unavailable probe 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
|
+
stream.last_broadcast_presence = @node.clone unless self['to']
|
|
18
|
+
unless self['type'].nil?
|
|
19
|
+
raise StanzaErrors::BadRequest.new(self, 'modify')
|
|
20
|
+
end
|
|
21
|
+
dir = outbound? ? 'outbound' : 'inbound'
|
|
22
|
+
method("#{dir}_broadcast_presence").call
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def outbound?
|
|
26
|
+
stream.class != Vines::Stream::Server
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def inbound?
|
|
30
|
+
stream.class == Vines::Stream::Server
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def outbound_broadcast_presence
|
|
34
|
+
self['from'] = stream.user.jid.to_s
|
|
35
|
+
to, type = %w[to type].map {|a| (self[a] || '').strip }
|
|
36
|
+
initial = to.empty? && type.empty? && !stream.available?
|
|
37
|
+
|
|
38
|
+
recipients = if to.empty?
|
|
39
|
+
stream.available_subscribers
|
|
40
|
+
else
|
|
41
|
+
stream.user.subscribed_from?(to) ? router.available_resources(to) : []
|
|
42
|
+
end
|
|
43
|
+
broadcast(recipients + router.available_resources(stream.user.jid))
|
|
44
|
+
|
|
45
|
+
if initial
|
|
46
|
+
stream.available_subscribed_to_resources.each do |recipient|
|
|
47
|
+
if recipient.last_broadcast_presence
|
|
48
|
+
el = recipient.last_broadcast_presence.clone
|
|
49
|
+
el['to'] = stream.user.jid.to_s
|
|
50
|
+
el['from'] = recipient.user.jid.to_s
|
|
51
|
+
stream.write(el)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
stream.available!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
stream.remote_subscribers(to).each do |contact|
|
|
58
|
+
node = @node.clone
|
|
59
|
+
node['to'] = contact.jid.bare.to_s
|
|
60
|
+
router.route(node) rescue nil # ignore RemoteServerNotFound
|
|
61
|
+
send_probe(contact.jid.bare) if initial
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def inbound_broadcast_presence
|
|
66
|
+
broadcast(router.available_resources(self['to']))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def send_probe(to)
|
|
72
|
+
to = JID.new(to)
|
|
73
|
+
doc = Document.new
|
|
74
|
+
probe = doc.create_element('presence',
|
|
75
|
+
'from' => stream.user.jid.bare.to_s,
|
|
76
|
+
'id' => Kit.uuid,
|
|
77
|
+
'to' => to.bare.to_s,
|
|
78
|
+
'type' => 'probe')
|
|
79
|
+
router.route(probe)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def send_subscribed_roster_push(recipient, jid, state)
|
|
83
|
+
doc = Document.new
|
|
84
|
+
node = doc.create_element('iq',
|
|
85
|
+
'id' => Kit.uuid,
|
|
86
|
+
'to' => recipient.user.jid.to_s,
|
|
87
|
+
'type' => 'set')
|
|
88
|
+
node << doc.create_element('query', 'xmlns' => NAMESPACES[:roster]) do |query|
|
|
89
|
+
query << doc.create_element('item', 'jid' => jid.to_s, 'subscription' => state)
|
|
90
|
+
end
|
|
91
|
+
recipient.write(node)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def auto_reply_to_subscription_request(from, type)
|
|
95
|
+
doc = Document.new
|
|
96
|
+
node = doc.create_element('presence') do |el|
|
|
97
|
+
el['from'] = from.to_s
|
|
98
|
+
el['id'] = self['id'] if self['id']
|
|
99
|
+
el['to'] = stream.user.jid.bare.to_s
|
|
100
|
+
el['type'] = type
|
|
101
|
+
end
|
|
102
|
+
stream.write(node)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Validate that the incoming stanza has a 'to' attribute and strip any
|
|
106
|
+
# resource part from it so it's a bare jid. Return the bare JID object
|
|
107
|
+
# that was stamped.
|
|
108
|
+
def stamp_to
|
|
109
|
+
to = (self['to'] || '').strip
|
|
110
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if to.empty?
|
|
111
|
+
to = JID.new(to).bare
|
|
112
|
+
malformed = [to.node, to.domain].any? {|part| (part || '').strip.empty? }
|
|
113
|
+
raise StanzaErrors::JidMalformed.new(self, 'modify') if malformed
|
|
114
|
+
self['to'] = to.to_s
|
|
115
|
+
to
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Presence
|
|
6
|
+
class Error < Presence
|
|
7
|
+
register "/presence[@type='error']"
|
|
8
|
+
|
|
9
|
+
def process
|
|
10
|
+
inbound? ? process_inbound : process_outbound
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_outbound
|
|
14
|
+
# FIXME Implement error handling
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process_inbound
|
|
18
|
+
# FIXME Implement error handling
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Vines
|
|
4
|
+
class Stanza
|
|
5
|
+
class Presence
|
|
6
|
+
class Probe < Presence
|
|
7
|
+
register "/presence[@type='probe']"
|
|
8
|
+
|
|
9
|
+
def process
|
|
10
|
+
inbound? ? process_inbound : process_outbound
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_outbound
|
|
14
|
+
self['from'] = stream.user.jid.to_s
|
|
15
|
+
local? ? process_inbound : route
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process_inbound
|
|
19
|
+
to = (self['to'] || '').strip
|
|
20
|
+
raise StanzaErrors::BadRequest.new(self, 'modify') if to.empty?
|
|
21
|
+
to = JID.new(to)
|
|
22
|
+
|
|
23
|
+
user = storage(to.domain).find_user(to)
|
|
24
|
+
unless user && user.subscribed_from?(stream.user.jid)
|
|
25
|
+
auto_reply_to_subscription_request(to.bare, 'unsubscribed')
|
|
26
|
+
else
|
|
27
|
+
router.available_resources(to).each do |recipient|
|
|
28
|
+
el = recipient.last_broadcast_presence.clone
|
|
29
|
+
el['from'] = recipient.user.jid.to_s
|
|
30
|
+
el['to'] = stream.user.jid.to_s
|
|
31
|
+
stream.write(el)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|