vines 0.3.2 → 0.4.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.
Files changed (115) hide show
  1. data/README +5 -9
  2. data/Rakefile +11 -9
  3. data/conf/config.rb +30 -4
  4. data/lib/vines/cluster/connection.rb +26 -0
  5. data/lib/vines/cluster/publisher.rb +55 -0
  6. data/lib/vines/cluster/pubsub.rb +92 -0
  7. data/lib/vines/cluster/sessions.rb +125 -0
  8. data/lib/vines/cluster/subscriber.rb +108 -0
  9. data/lib/vines/cluster.rb +246 -0
  10. data/lib/vines/command/init.rb +21 -24
  11. data/lib/vines/config/host.rb +48 -8
  12. data/lib/vines/config/port.rb +5 -0
  13. data/lib/vines/config/pubsub.rb +108 -0
  14. data/lib/vines/config.rb +74 -20
  15. data/lib/vines/jid.rb +14 -0
  16. data/lib/vines/router.rb +69 -55
  17. data/lib/vines/stanza/iq/disco_info.rb +22 -9
  18. data/lib/vines/stanza/iq/disco_items.rb +6 -3
  19. data/lib/vines/stanza/iq/ping.rb +1 -1
  20. data/lib/vines/stanza/iq/private_storage.rb +4 -8
  21. data/lib/vines/stanza/iq/roster.rb +6 -14
  22. data/lib/vines/stanza/iq/session.rb +2 -7
  23. data/lib/vines/stanza/iq/vcard.rb +4 -6
  24. data/lib/vines/stanza/iq/version.rb +1 -1
  25. data/lib/vines/stanza/iq.rb +8 -10
  26. data/lib/vines/stanza/presence/subscribe.rb +3 -11
  27. data/lib/vines/stanza/presence/subscribed.rb +16 -29
  28. data/lib/vines/stanza/presence/unsubscribe.rb +3 -15
  29. data/lib/vines/stanza/presence/unsubscribed.rb +3 -16
  30. data/lib/vines/stanza/presence.rb +30 -0
  31. data/lib/vines/stanza/pubsub/create.rb +39 -0
  32. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  33. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  34. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  35. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  36. data/lib/vines/stanza/pubsub.rb +22 -0
  37. data/lib/vines/stanza.rb +72 -22
  38. data/lib/vines/storage/couchdb.rb +46 -65
  39. data/lib/vines/storage/local.rb +20 -14
  40. data/lib/vines/storage/mongodb.rb +132 -0
  41. data/lib/vines/storage/null.rb +39 -0
  42. data/lib/vines/storage/redis.rb +61 -68
  43. data/lib/vines/storage/sql.rb +73 -69
  44. data/lib/vines/storage.rb +1 -1
  45. data/lib/vines/stream/client/bind.rb +2 -2
  46. data/lib/vines/stream/client/session.rb +71 -16
  47. data/lib/vines/stream/component/handshake.rb +1 -0
  48. data/lib/vines/stream/component/ready.rb +2 -2
  49. data/lib/vines/stream/http/session.rb +2 -0
  50. data/lib/vines/stream/http.rb +0 -6
  51. data/lib/vines/stream/server/final_restart.rb +1 -0
  52. data/lib/vines/stream/server/outbound/final_features.rb +1 -0
  53. data/lib/vines/stream/server/ready.rb +6 -2
  54. data/lib/vines/stream/server.rb +4 -3
  55. data/lib/vines/stream.rb +10 -6
  56. data/lib/vines/version.rb +1 -1
  57. data/lib/vines.rb +48 -22
  58. data/test/cluster/publisher_test.rb +45 -0
  59. data/test/cluster/sessions_test.rb +54 -0
  60. data/test/cluster/subscriber_test.rb +94 -0
  61. data/test/config/host_test.rb +100 -21
  62. data/test/config/pubsub_test.rb +181 -0
  63. data/test/config_test.rb +225 -43
  64. data/test/jid_test.rb +7 -0
  65. data/test/router_test.rb +181 -9
  66. data/test/stanza/iq/disco_info_test.rb +8 -6
  67. data/test/stanza/iq/disco_items_test.rb +3 -3
  68. data/test/stanza/iq/private_storage_test.rb +8 -19
  69. data/test/stanza/iq/roster_test.rb +1 -1
  70. data/test/stanza/iq/session_test.rb +3 -6
  71. data/test/stanza/iq/vcard_test.rb +6 -2
  72. data/test/stanza/iq/version_test.rb +3 -2
  73. data/test/stanza/iq_test.rb +5 -5
  74. data/test/stanza/message_test.rb +3 -2
  75. data/test/stanza/presence/probe_test.rb +2 -1
  76. data/test/stanza/pubsub/create_test.rb +138 -0
  77. data/test/stanza/pubsub/delete_test.rb +142 -0
  78. data/test/stanza/pubsub/publish_test.rb +373 -0
  79. data/test/stanza/pubsub/subscribe_test.rb +186 -0
  80. data/test/stanza/pubsub/unsubscribe_test.rb +179 -0
  81. data/test/stanza_test.rb +2 -1
  82. data/test/storage/local_test.rb +26 -25
  83. data/test/storage/mock_mongo.rb +40 -0
  84. data/test/storage/mock_redis.rb +98 -0
  85. data/test/storage/mongodb_test.rb +81 -0
  86. data/test/storage/null_test.rb +30 -0
  87. data/test/storage/redis_test.rb +3 -36
  88. data/test/stream/component/handshake_test.rb +4 -0
  89. data/test/stream/component/ready_test.rb +2 -1
  90. data/test/stream/server/ready_test.rb +7 -1
  91. data/web/404.html +5 -3
  92. data/web/chat/coffeescripts/chat.coffee +9 -5
  93. data/web/chat/javascripts/app.js +1 -1
  94. data/web/chat/javascripts/chat.js +14 -8
  95. data/web/chat/stylesheets/chat.css +4 -1
  96. data/web/lib/coffeescripts/button.coffee +9 -5
  97. data/web/lib/coffeescripts/filter.coffee +1 -1
  98. data/web/lib/coffeescripts/login.coffee +14 -1
  99. data/web/lib/coffeescripts/session.coffee +8 -11
  100. data/web/lib/images/dark-gray.png +0 -0
  101. data/web/lib/images/light-gray.png +0 -0
  102. data/web/lib/images/logo-large.png +0 -0
  103. data/web/lib/images/logo-small.png +0 -0
  104. data/web/lib/images/white.png +0 -0
  105. data/web/lib/javascripts/base.js +9 -8
  106. data/web/lib/javascripts/button.js +20 -12
  107. data/web/lib/javascripts/filter.js +1 -1
  108. data/web/lib/javascripts/icons.js +7 -1
  109. data/web/lib/javascripts/jquery.js +4 -4
  110. data/web/lib/javascripts/login.js +16 -2
  111. data/web/lib/javascripts/raphael.js +5 -7
  112. data/web/lib/javascripts/session.js +10 -14
  113. data/web/lib/stylesheets/base.css +7 -11
  114. data/web/lib/stylesheets/login.css +31 -27
  115. metadata +100 -34
@@ -7,29 +7,21 @@ module Vines
7
7
  register "/presence[@type='subscribe']"
8
8
 
9
9
  def process
10
+ stamp_from
10
11
  inbound? ? process_inbound : process_outbound
11
12
  end
12
13
 
13
14
  def process_outbound
14
- self['from'] = stream.user.jid.bare.to_s
15
15
  to = stamp_to
16
-
17
16
  stream.user.request_subscription(to)
18
17
  storage.save_user(stream.user)
19
18
  stream.update_user_streams(stream.user)
20
-
21
19
  local? ? process_inbound : route
22
-
23
- contact = stream.user.contact(to)
24
- stream.interested_resources(stream.user.jid).each do |recipient|
25
- contact.send_roster_push(recipient)
26
- end
20
+ send_roster_push(to)
27
21
  end
28
22
 
29
23
  def process_inbound
30
- self['from'] = stream.user.jid.bare.to_s
31
24
  to = stamp_to
32
-
33
25
  contact = storage(to.domain).find_user(to)
34
26
  if contact.nil?
35
27
  auto_reply_to_subscription_request(to, 'unsubscribed')
@@ -40,7 +32,7 @@ module Vines
40
32
  if recipients.empty?
41
33
  # TODO store subscription request per RFC 6121 3.1.3 #4
42
34
  else
43
- recipients.each {|stream| stream.write(@node) }
35
+ broadcast_to_available_resources([@node], to)
44
36
  end
45
37
  end
46
38
  end
@@ -7,56 +7,43 @@ module Vines
7
7
  register "/presence[@type='subscribed']"
8
8
 
9
9
  def process
10
+ stamp_from
10
11
  inbound? ? process_inbound : process_outbound
11
12
  end
12
13
 
13
14
  def process_outbound
14
- self['from'] = stream.user.jid.bare.to_s
15
15
  to = stamp_to
16
-
17
16
  stream.user.add_subscription_from(to)
18
17
  storage.save_user(stream.user)
19
18
  stream.update_user_streams(stream.user)
20
-
21
19
  local? ? process_inbound : route
22
-
23
- contact = stream.user.contact(to)
24
- stream.interested_resources(stream.user.jid).each do |recipient|
25
- contact.send_roster_push(recipient)
26
- end
27
-
28
- presences = stream.available_resources(stream.user.jid).map do |c|
29
- c.last_broadcast_presence.clone.tap do |node|
30
- node['from'] = c.user.jid.to_s
31
- node['id'] = Kit.uuid
32
- node['to'] = to.to_s
33
- end
34
- end
35
-
36
- if local?
37
- stream.available_resources(to).each do |recipient|
38
- presences.each {|el| recipient.write(el) }
39
- end
40
- else
41
- presences.each {|el| router.route(el) }
42
- end
20
+ send_roster_push(to)
21
+ send_known_presence(to)
43
22
  end
44
23
 
45
24
  def process_inbound
46
- self['from'] = stream.user.jid.bare.to_s
47
25
  to = stamp_to
48
-
49
26
  user = storage(to.domain).find_user(to)
50
27
  contact = user.contact(stream.user.jid) if user
51
28
  return unless contact && contact.can_subscribe?
52
29
  contact.subscribe_to
53
30
  storage(to.domain).save_user(user)
54
31
  stream.update_user_streams(user)
32
+ broadcast_subscription_change(contact)
33
+ end
34
+
35
+ private
55
36
 
56
- stream.interested_resources(to).each do |recipient|
57
- recipient.write(@node)
58
- contact.send_roster_push(recipient)
37
+ # After approving a contact's subscription to this user's presence,
38
+ # broadcast this user's most recent presence stanzas to the contact.
39
+ def send_known_presence(to)
40
+ stanzas = stream.available_resources(stream.user.jid).map do |stream|
41
+ stream.last_broadcast_presence.clone.tap do |node|
42
+ node['from'] = stream.user.jid.to_s
43
+ node['id'] = Kit.uuid
44
+ end
59
45
  end
46
+ broadcast_to_available_resources(stanzas, to)
60
47
  end
61
48
  end
62
49
  end
@@ -7,41 +7,29 @@ module Vines
7
7
  register "/presence[@type='unsubscribe']"
8
8
 
9
9
  def process
10
+ stamp_from
10
11
  inbound? ? process_inbound : process_outbound
11
12
  end
12
13
 
13
14
  def process_outbound
14
- self['from'] = stream.user.jid.bare.to_s
15
15
  to = stamp_to
16
-
17
16
  return unless stream.user.subscribed_to?(to)
18
17
  stream.user.remove_subscription_to(to)
19
18
  storage.save_user(stream.user)
20
19
  stream.update_user_streams(stream.user)
21
-
22
20
  local? ? process_inbound : route
23
-
24
- contact = stream.user.contact(to)
25
- stream.interested_resources(stream.user.jid).each do |recipient|
26
- contact.send_roster_push(recipient)
27
- end
21
+ send_roster_push(to)
28
22
  end
29
23
 
30
24
  def process_inbound
31
- self['from'] = stream.user.jid.bare.to_s
32
25
  to = stamp_to
33
-
34
26
  user = storage(to.domain).find_user(to)
35
27
  return unless user && user.subscribed_from?(stream.user.jid)
36
28
  contact = user.contact(stream.user.jid)
37
29
  contact.unsubscribe_from
38
30
  storage(to.domain).save_user(user)
39
31
  stream.update_user_streams(user)
40
-
41
- stream.interested_resources(to).each do |recipient|
42
- recipient.write(@node)
43
- contact.send_roster_push(recipient)
44
- end
32
+ broadcast_subscription_change(contact)
45
33
  send_unavailable(to, stream.user.jid.bare)
46
34
  end
47
35
  end
@@ -7,43 +7,30 @@ module Vines
7
7
  register "/presence[@type='unsubscribed']"
8
8
 
9
9
  def process
10
+ stamp_from
10
11
  inbound? ? process_inbound : process_outbound
11
12
  end
12
13
 
13
14
  def process_outbound
14
- self['from'] = stream.user.jid.bare.to_s
15
15
  to = stamp_to
16
-
17
16
  return unless stream.user.subscribed_from?(to)
18
17
  send_unavailable(stream.user.jid, to)
19
-
20
18
  stream.user.remove_subscription_from(to)
21
19
  storage.save_user(stream.user)
22
20
  stream.update_user_streams(stream.user)
23
-
24
21
  local? ? process_inbound : route
25
-
26
- contact = stream.user.contact(to)
27
- stream.interested_resources(stream.user.jid).each do |recipient|
28
- contact.send_roster_push(recipient)
29
- end
22
+ send_roster_push(to)
30
23
  end
31
24
 
32
25
  def process_inbound
33
- self['from'] = stream.user.jid.bare.to_s
34
26
  to = stamp_to
35
-
36
27
  user = storage(to.domain).find_user(to)
37
28
  return unless user && user.subscribed_to?(stream.user.jid)
38
29
  contact = user.contact(stream.user.jid)
39
30
  contact.unsubscribe_to
40
31
  storage(to.domain).save_user(user)
41
32
  stream.update_user_streams(user)
42
-
43
- stream.interested_resources(to).each do |recipient|
44
- recipient.write(@node)
45
- contact.send_roster_push(recipient)
46
- end
33
+ broadcast_subscription_change(contact)
47
34
  end
48
35
  end
49
36
  end
@@ -96,6 +96,28 @@ module Vines
96
96
  stream.write(node)
97
97
  end
98
98
 
99
+ # Send the contact's roster item to the current user's interested streams.
100
+ # Roster pushes are required, following presence subscription updates, to
101
+ # notify the user's clients of the contact's current state.
102
+ def send_roster_push(to)
103
+ contact = stream.user.contact(to)
104
+ stream.interested_resources(stream.user.jid).each do |recipient|
105
+ contact.send_roster_push(recipient)
106
+ end
107
+ end
108
+
109
+ # Notify the current user's interested streams of a contact's subscription
110
+ # state change as a result of receiving a subscribed, unsubscribe, or
111
+ # unsubscribed presence stanza.
112
+ def broadcast_subscription_change(contact)
113
+ stamp_from
114
+ stream.interested_resources(stamp_to).each do |recipient|
115
+ @node['to'] = recipient.user.jid.to_s
116
+ recipient.write(@node)
117
+ contact.send_roster_push(recipient)
118
+ end
119
+ end
120
+
99
121
  # Validate that the incoming stanza has a 'to' attribute and strip any
100
122
  # resource part from it so it's a bare jid. Return the bare JID object
101
123
  # that was stamped.
@@ -106,6 +128,14 @@ module Vines
106
128
  self['to'] = bare.to_s
107
129
  end
108
130
  end
131
+
132
+ # Presence subscription stanzas must be addressed from the user's bare
133
+ # JID. Return the user's bare JID object that was stamped.
134
+ def stamp_from
135
+ stream.user.jid.bare.tap do |bare|
136
+ self['from'] = bare.to_s
137
+ end
138
+ end
109
139
  end
110
140
  end
111
141
  end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class PubSub
6
+ class Create < PubSub
7
+ NS = NAMESPACES[:pubsub]
8
+
9
+ register "/iq[@id and @type='set']/ns:pubsub/ns:create", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq || !allowed?
13
+ validate_to_address
14
+
15
+ node = self.xpath('ns:pubsub/ns:create', 'ns' => NS)
16
+ raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
17
+ node = node.first
18
+
19
+ id = (node['node'] || '').strip
20
+ id = Kit.uuid if id.empty?
21
+ raise StanzaErrors::Conflict.new(self, 'cancel') if pubsub.node?(id)
22
+ pubsub.add_node(id)
23
+ send_result_iq(id)
24
+ end
25
+
26
+ private
27
+
28
+ def send_result_iq(id)
29
+ el = to_result
30
+ el << el.document.create_element('pubsub') do |node|
31
+ node.default_namespace = NS
32
+ node << el.document.create_element('create', 'node' => id)
33
+ end
34
+ stream.write(el)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class PubSub
6
+ class Delete < PubSub
7
+ NS = NAMESPACES[:pubsub]
8
+
9
+ register "/iq[@id and @type='set']/ns:pubsub/ns:delete", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq || !allowed?
13
+ validate_to_address
14
+
15
+ node = self.xpath('ns:pubsub/ns:delete', 'ns' => NS)
16
+ raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
17
+ node = node.first
18
+
19
+ id = node['node']
20
+ raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
21
+
22
+ pubsub.publish(id, message(id))
23
+ pubsub.delete_node(id)
24
+ stream.write(to_result)
25
+ end
26
+
27
+ private
28
+
29
+ def message(id)
30
+ doc = Document.new
31
+ doc.create_element('message') do |node|
32
+ node << node.document.create_element('event') do |event|
33
+ event.default_namespace = NAMESPACES[:pubsub_event]
34
+ event << node.document.create_element('delete', 'node' => id)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class PubSub
6
+ class Publish < PubSub
7
+ NS = NAMESPACES[:pubsub]
8
+
9
+ register "/iq[@id and @type='set']/ns:pubsub/ns:publish", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq || !allowed?
13
+ validate_to_address
14
+
15
+ node = self.xpath('ns:pubsub/ns:publish', 'ns' => NS)
16
+ raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
17
+ node = node.first
18
+ id = node['node']
19
+
20
+ raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
21
+
22
+ item = node.xpath('ns:item', 'ns' => NS)
23
+ raise StanzaErrors::BadRequest.new(self, 'modify') unless item.size == 1
24
+ item = item.first
25
+ unless item['id']
26
+ item['id'] = Kit.uuid
27
+ include_item = true
28
+ end
29
+
30
+ raise StanzaErrors::BadRequest.new(self, 'modify') unless item.elements.size == 1
31
+ pubsub.publish(id, message(id, item))
32
+ send_result_iq(id, include_item ? item : nil)
33
+ end
34
+
35
+ private
36
+
37
+ def message(node, item)
38
+ doc = Document.new
39
+ doc.create_element('message') do |message|
40
+ message << doc.create_element('event') do |event|
41
+ event.default_namespace = NAMESPACES[:pubsub_event]
42
+ event << doc.create_element('items', 'node' => node) do |items|
43
+ items << doc.create_element('item', 'id' => item['id'], 'publisher' => stream.user.jid.to_s) do |el|
44
+ el << item.elements.first
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def send_result_iq(node, item)
52
+ result = to_result
53
+ if item
54
+ result << result.document.create_element('pubsub') do |pubsub|
55
+ pubsub.default_namespace = NS
56
+ pubsub << result.document.create_element('publish', 'node' => node) do |publish|
57
+ publish << result.document.create_element('item', 'id' => item['id'])
58
+ end
59
+ end
60
+ end
61
+ stream.write(result)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class PubSub
6
+ class Subscribe < PubSub
7
+ NS = NAMESPACES[:pubsub]
8
+
9
+ register "/iq[@id and @type='set']/ns:pubsub/ns:subscribe", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq || !allowed?
13
+ validate_to_address
14
+
15
+ node = self.xpath('ns:pubsub/ns:subscribe', 'ns' => NS)
16
+ raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
17
+ node = node.first
18
+ id, jid = node['node'], JID.new(node['jid'])
19
+
20
+ raise StanzaErrors::BadRequest.new(self, 'modify') unless stream.user.jid.bare == jid.bare
21
+ raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
22
+ raise StanzaErrors::PolicyViolation.new(self, 'wait') if pubsub.subscribed?(id, jid)
23
+
24
+ pubsub.subscribe(id, jid)
25
+ send_result_iq(id, jid)
26
+ end
27
+
28
+ private
29
+
30
+ def send_result_iq(id, jid)
31
+ result = to_result
32
+ result << result.document.create_element('pubsub') do |node|
33
+ node.default_namespace = NS
34
+ node << result.document.create_element('subscription',
35
+ 'node' => id,
36
+ 'jid' => jid.to_s,
37
+ 'subscription' => 'subscribed')
38
+ end
39
+ stream.write(result)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class PubSub
6
+ class Unsubscribe < PubSub
7
+ NS = NAMESPACES[:pubsub]
8
+
9
+ register "/iq[@id and @type='set']/ns:pubsub/ns:unsubscribe", 'ns' => NS
10
+
11
+ def process
12
+ return if route_iq || !allowed?
13
+ validate_to_address
14
+
15
+ node = self.xpath('ns:pubsub/ns:unsubscribe', 'ns' => NS)
16
+ raise StanzaErrors::BadRequest.new(self, 'modify') if node.size != 1
17
+ node = node.first
18
+ id, jid = node['node'], JID.new(node['jid'])
19
+
20
+ raise StanzaErrors::Forbidden.new(self, 'auth') unless stream.user.jid.bare == jid.bare
21
+ raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless pubsub.node?(id)
22
+ raise StanzaErrors::UnexpectedRequest.new(self, 'cancel') unless pubsub.subscribed?(id, jid)
23
+
24
+ pubsub.unsubscribe(id, jid)
25
+ stream.write(to_result)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stanza
5
+ class PubSub < Iq
6
+
7
+ private
8
+
9
+ # Return the Config::PubSub system for the domain to which this stanza is
10
+ # addressed or nil if it's not to a pubsub subdomain.
11
+ def pubsub
12
+ stream.config.pubsub(validate_to)
13
+ end
14
+
15
+ # Raise feature-not-implemented if this stanza is addressed to the chat
16
+ # server itself, rather than a pubsub subdomain.
17
+ def validate_to_address
18
+ raise StanzaErrors::FeatureNotImplemented.new(self, 'cancel') unless pubsub
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/vines/stanza.rb CHANGED
@@ -6,12 +6,9 @@ module Vines
6
6
 
7
7
  attr_reader :stream
8
8
 
9
- EMPTY = ''.freeze
10
- FROM = 'from'.freeze
11
- MESSAGE = 'message'.freeze
12
- TO = 'to'.freeze
13
-
14
- ROUTABLE_STANZAS = %w[message iq presence].freeze
9
+ EMPTY = ''.freeze
10
+ FROM, MESSAGE, TO = %w[from message to].map {|s| s.freeze }
11
+ ROUTABLE_STANZAS = %w[message iq presence].freeze
15
12
 
16
13
  @@types = {}
17
14
 
@@ -45,7 +42,7 @@ module Vines
45
42
  # if it's destined for a remote domain or external component.
46
43
  def local?
47
44
  return true unless ROUTABLE_STANZAS.include?(@node.name)
48
- to = JID.new(@node['to'])
45
+ to = JID.new(@node[TO])
49
46
  to.empty? || local_jid?(to)
50
47
  end
51
48
 
@@ -53,6 +50,14 @@ module Vines
53
50
  stream.config.local_jid?(*jids)
54
51
  end
55
52
 
53
+ # Return true if this stanza is addressed to a pubsub subdomain hosted
54
+ # at this server. This helps differentiate between IQ stanzas addressed
55
+ # to the server and stanzas addressed to pubsub domains, both of which must
56
+ # be handled locally and not routed.
57
+ def to_pubsub_domain?
58
+ stream.config.pubsub?(validate_to)
59
+ end
60
+
56
61
  def route
57
62
  stream.router.route(@node)
58
63
  end
@@ -73,25 +78,17 @@ module Vines
73
78
  # recipient's available resources. Route the stanza to a remote server if
74
79
  # the recipient isn't hosted locally.
75
80
  def send_unavailable(from, to)
76
- recipients = router.available_resources(to, from) if local_jid?(to)
77
-
78
- router.available_resources(from, to).each do |stream|
79
- el = unavailable(stream.user.jid, to)
80
- if local_jid?(to)
81
- recipients.each {|recipient| recipient.write(el) }
82
- else
83
- router.route(el)
84
- end
85
- end
81
+ available = router.available_resources(from, to)
82
+ stanzas = available.map {|stream| unavailable(stream.user.jid) }
83
+ broadcast_to_available_resources(stanzas, to)
86
84
  end
87
85
 
88
- # Return an unavailable presence stanza addressed to the given JID.
89
- def unavailable(from, to)
86
+ # Return an unavailable presence stanza addressed from the given JID.
87
+ def unavailable(from)
90
88
  doc = Document.new
91
89
  doc.create_element('presence',
92
90
  'from' => from.to_s,
93
91
  'id' => Kit.uuid,
94
- 'to' => to.to_s,
95
92
  'type' => 'unavailable')
96
93
  end
97
94
 
@@ -115,11 +112,64 @@ module Vines
115
112
 
116
113
  private
117
114
 
115
+ # Send the stanzas to the destination JID, routing to a s2s stream
116
+ # if the address is remote. This method properly stamps the to address
117
+ # on each stanza before it's sent. The caller must set the from address.
118
+ def broadcast_to_available_resources(stanzas, to)
119
+ return if send_to_remote(stanzas, to)
120
+ send_to_recipients(stanzas, stream.available_resources(to))
121
+ end
122
+
123
+ # Send the stanzas to the destination JID, routing to a s2s stream
124
+ # if the address is remote. This method properly stamps the to address
125
+ # on each stanza before it's sent. The caller must set the from address.
126
+ def broadcast_to_interested_resources(stanzas, to)
127
+ return if send_to_remote(stanzas, to)
128
+ send_to_recipients(stanzas, stream.interested_resources(to))
129
+ end
130
+
131
+ # Route the stanzas to a remote server, stamping a bare JID as the
132
+ # to address. Bare JIDs are required for presence subscription stanzas
133
+ # sent to the remote contact's server. Return true if the stanzas were
134
+ # routed, false if they must be delivered locally.
135
+ def send_to_remote(stanzas, to)
136
+ return false if local_jid?(to)
137
+ to = JID.new(to)
138
+ stanzas.each do |el|
139
+ el[TO] = to.bare.to_s
140
+ router.route(el)
141
+ end
142
+ true
143
+ end
144
+
145
+ # Send the stanzas to the local recipient streams, stamping a full JID as
146
+ # the to address. It's important to use full JIDs, even when sending to
147
+ # local clients, because the stanzas may be routed to other cluster nodes
148
+ # for delivery. We need the receiving cluster node to send the stanza just
149
+ # to this full JID, not to lookup all JIDs for this user.
150
+ def send_to_recipients(stanzas, recipients)
151
+ recipients.each do |recipient|
152
+ stanzas.each do |el|
153
+ el[TO] = recipient.user.jid.to_s
154
+ recipient.write(el)
155
+ end
156
+ end
157
+ end
158
+
159
+ # Return true if the to and from JIDs are allowed to communicate with one
160
+ # another based on the cross_domain_messages setting in conf/config.rb. If
161
+ # a domain's users are isolated to sending messages only within their own
162
+ # domain, pubsub stanzas must not be processed from remote JIDs.
163
+ def allowed?
164
+ stream.config.allowed?(validate_to || stream.domain, stream.user.jid)
165
+ end
166
+
118
167
  def validate_address(attr)
119
168
  jid = (self[attr] || EMPTY)
120
169
  return if jid.empty?
121
- JID.new(jid) rescue
122
- raise StanzaErrors::JidMalformed.new(self, 'modify')
170
+ JID.new(jid)
171
+ rescue
172
+ raise StanzaErrors::JidMalformed.new(self, 'modify')
123
173
  end
124
174
  end
125
175
  end