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