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
data/lib/vines/config.rb CHANGED
@@ -9,7 +9,7 @@ module Vines
9
9
  class Config
10
10
  LOG_LEVELS = %w[debug info warn error fatal].freeze
11
11
 
12
- attr_reader :vhosts
12
+ attr_reader :router, :vhosts
13
13
 
14
14
  @@instance = nil
15
15
  def self.configure(&block)
@@ -21,7 +21,9 @@ module Vines
21
21
  end
22
22
 
23
23
  def initialize(&block)
24
- @vhosts, @ports = {}, {}
24
+ @vhosts, @ports, @cluster = {}, {}, nil
25
+ @null = Storage::Null.new
26
+ @router = Router.new(self)
25
27
  instance_eval(&block)
26
28
  raise "must define at least one virtual host" if @vhosts.empty?
27
29
  end
@@ -31,7 +33,7 @@ module Vines
31
33
  dupes = names.uniq.size != names.size || (@vhosts.keys & names).any?
32
34
  raise "one host definition per domain allowed" if dupes
33
35
  names.each do |name|
34
- @vhosts[name] = Host.new(name, &block)
36
+ @vhosts[name] = Host.new(self, name, &block)
35
37
  end
36
38
  end
37
39
 
@@ -45,6 +47,12 @@ module Vines
45
47
  end
46
48
  end
47
49
 
50
+ def cluster(&block)
51
+ return @cluster unless block
52
+ raise "one cluster definition allowed" if @cluster
53
+ @cluster = Cluster.new(self, &block)
54
+ end
55
+
48
56
  def log(level)
49
57
  const = Logger.const_get(level.to_s.upcase) rescue nil
50
58
  unless LOG_LEVELS.include?(level.to_s) && const
@@ -59,10 +67,30 @@ module Vines
59
67
 
60
68
  # Return true if the domain is virtual hosted by this server.
61
69
  def vhost?(domain)
62
- @vhosts.key?(domain)
70
+ @vhosts.key?(domain.to_s)
71
+ end
72
+
73
+ # Returns the storage system for the domain or a Storage::Null instance if
74
+ # the domain is not hosted at this server.
75
+ def storage(domain)
76
+ host = @vhosts[domain.to_s]
77
+ host ? host.storage : @null
78
+ end
79
+
80
+ # Returns the PubSub system for the domain or nil if pubsub is not enabled
81
+ # for this domain.
82
+ def pubsub(domain)
83
+ host = @vhosts.values.find {|host| host.pubsub?(domain) }
84
+ host.pubsubs[domain.to_s] if host
85
+ end
86
+
87
+ # Return true if the domain is a pubsub service hosted at a virtual host
88
+ # at this server.
89
+ def pubsub?(domain)
90
+ @vhosts.values.any? {|host| host.pubsub?(domain) }
63
91
  end
64
92
 
65
- # Return true if all JID's belong to components hosted by this server.
93
+ # Return true if all JIDs belong to components hosted by this server.
66
94
  def component?(*jids)
67
95
  !jids.flatten.index do |jid|
68
96
  !component_password(JID.new(jid).domain)
@@ -75,7 +103,7 @@ module Vines
75
103
  host.password(domain) if host
76
104
  end
77
105
 
78
- # Return true if all of the JID's are hosted by this server.
106
+ # Return true if all of the JIDs are hosted by this server.
79
107
  def local_jid?(*jids)
80
108
  !jids.flatten.index do |jid|
81
109
  !vhost?(JID.new(jid).domain)
@@ -84,13 +112,20 @@ module Vines
84
112
 
85
113
  # Return true if private XML fragment storage is enabled for this domain.
86
114
  def private_storage?(domain)
87
- @vhosts[domain].private_storage?
115
+ host = @vhosts[domain.to_s]
116
+ host.private_storage? if host
88
117
  end
89
118
 
90
119
  # Returns true if server-to-server connections are allowed with the
91
120
  # given domain.
92
121
  def s2s?(domain)
93
- @ports[:server] && @ports[:server].hosts.include?(domain)
122
+ @ports[:server] && @ports[:server].hosts.include?(domain.to_s)
123
+ end
124
+
125
+ # Return true if the server is a member of a cluster, serving the same
126
+ # domains from different machines.
127
+ def cluster?
128
+ !!@cluster
94
129
  end
95
130
 
96
131
  # Retrieve the Port subclass with this name:
@@ -99,16 +134,16 @@ module Vines
99
134
  @ports[name] or raise ArgumentError.new("no port named #{name}")
100
135
  end
101
136
 
102
- # Return true if the two JID's are allowed to send messages to each other.
137
+ # Return true if the two JIDs are allowed to send messages to each other.
103
138
  # Both domains must have enabled cross_domain_messages in their config files.
104
139
  def allowed?(to, from)
105
140
  to, from = JID.new(to), JID.new(from)
106
141
  return false if to.empty? || from.empty?
107
142
  return true if to.domain == from.domain # same domain always allowed
108
143
  return cross_domain?(to, from) if local_jid?(to, from) # both virtual hosted here
109
- return check_components(to, from) if component?(to, from) # component to component
110
- return check_component(to, from) if component?(to) # to component
111
- return check_component(from, to) if component?(from) # from component
144
+ return check_subdomains(to, from) if subdomain?(to, from) # component/pubsub to component/pubsub
145
+ return check_subdomain(to, from) if subdomain?(to) # to component/pubsub
146
+ return check_subdomain(from, to) if subdomain?(from) # from component/pubsub
112
147
  return cross_domain?(to) if local_jid?(to) # from is remote
113
148
  return cross_domain?(from) if local_jid?(from) # to is remote
114
149
  return false
@@ -116,25 +151,44 @@ module Vines
116
151
 
117
152
  private
118
153
 
119
- def check_components(to, from)
120
- comp1, comp2 = strip_domain(to), strip_domain(from)
121
- (comp1 == comp2) || cross_domain?(comp1, comp2)
154
+ # Return true if all of the JIDs are some kind of subdomain resource hosted
155
+ # here (either a component or a pubsub domain).
156
+ def subdomain?(*jids)
157
+ !jids.flatten.index do |jid|
158
+ !component?(jid) && !pubsub?(jid)
159
+ end
160
+ end
161
+
162
+ # Return true if the third-level subdomain JIDs (components and pubsubs)
163
+ # are allowed to communicate with each other. For example, a
164
+ # tea.wonderland.lit component should be allowed to send messages to
165
+ # pubsub.wonderland.lit because they share the second-level wonderland.lit
166
+ # domain.
167
+ def check_subdomains(to, from)
168
+ sub1, sub2 = strip_domain(to), strip_domain(from)
169
+ (sub1 == sub2) || cross_domain?(sub1, sub2)
122
170
  end
123
171
 
124
- def check_component(component_jid, jid)
125
- comp = strip_domain(component_jid)
172
+ # Return true if the third-level subdomain JID (component or pubsub) is
173
+ # allowed to communicate with the other JID. For example,
174
+ # pubsub.wonderland.lit should be allowed to send messages to
175
+ # alice@wonderland.lit because they share the second-level wonderland.lit
176
+ # domain.
177
+ def check_subdomain(subdomain, jid)
178
+ comp = strip_domain(subdomain)
126
179
  return true if comp.domain == jid.domain
127
180
  local_jid?(jid) ? cross_domain?(comp, jid) : cross_domain?(comp)
128
181
  end
129
182
 
130
- # Return the JID's domain with the first subdomain stripped off. For example,
131
- # alice@tea.wonderland.lit returns wonderland.lit.
183
+ # Return the third-level JID's domain with the first subdomain stripped off
184
+ # to create a second-level domain. For example, alice@tea.wonderland.lit
185
+ # returns wonderland.lit.
132
186
  def strip_domain(jid)
133
187
  domain = jid.domain.split('.').drop(1).join('.')
134
188
  JID.new(domain)
135
189
  end
136
190
 
137
- # Return true if all JID's are allowed to exchange cross domain messages.
191
+ # Return true if all JIDs are allowed to exchange cross domain messages.
138
192
  def cross_domain?(*jids)
139
193
  !jids.flatten.index do |jid|
140
194
  !@vhosts[jid.domain].cross_domain_messages?
data/lib/vines/jid.rb CHANGED
@@ -30,14 +30,28 @@ module Vines
30
30
  validate
31
31
  end
32
32
 
33
+ # Strip the resource part from this JID and return it as a new
34
+ # JID object. The new JID contains only the optional node part
35
+ # and the required domain part from the original. This JID remains
36
+ # unchanged.
33
37
  def bare
34
38
  JID.new(@node, @domain)
35
39
  end
36
40
 
41
+ # Return true if this is a bare JID without a resource part.
37
42
  def bare?
38
43
  @resource.nil?
39
44
  end
40
45
 
46
+ # Return true if this is a domain-only JID without a node or resource part.
47
+ def domain?
48
+ !empty? && to_s == @domain
49
+ end
50
+
51
+ # Return true if this JID is equal to the empty string ''. That is, it's
52
+ # missing the node, domain, and resource parts that form a valid JID. It
53
+ # makes for easier error handling to be able to create JID objects from
54
+ # strings and then check if they're empty rather than nil.
41
55
  def empty?
42
56
  to_s == ''
43
57
  end
data/lib/vines/router.rb CHANGED
@@ -4,72 +4,74 @@ module Vines
4
4
  # The router tracks all stream connections to the server for all clients,
5
5
  # servers, and components. It sends stanzas to the correct stream based on
6
6
  # the 'to' attribute. Router is a singleton, shared by all streams, that must
7
- # be accessed with +Router.instance+, not +Router.new+.
7
+ # be accessed with +Config#router+.
8
8
  class Router
9
+ EMPTY = [].freeze
9
10
 
10
11
  STREAM_TYPES = [:client, :server, :component].freeze
11
- STREAM_TYPES.each do |name|
12
- define_method "#{name}s" do
13
- @streams[name]
14
- end
15
- end
16
12
 
17
- @@instance = nil
18
- def self.instance
19
- @@instance ||= self.new
20
- end
21
-
22
- def initialize
23
- @config = nil
24
- @streams = Hash.new {|h,k| h[k] = [] }
13
+ def initialize(config)
14
+ @config = config
15
+ @clients, @servers, @components = {}, [], []
25
16
  @pending = Hash.new {|h,k| h[k] = [] }
26
17
  end
27
18
 
28
- # Returns streams for all connected resources for this JID. A
29
- # resource is considered connected after it has completed authentication
30
- # and resource binding.
31
- def connected_resources(jid, from)
19
+ # Returns streams for all connected resources for this JID. A resource is
20
+ # considered connected after it has completed authentication and resource
21
+ # binding.
22
+ def connected_resources(jid, from, proxies=true)
32
23
  jid, from = JID.new(jid), JID.new(from)
33
- clients.select do |stream|
34
- stream.connected? &&
35
- jid == (jid.bare? ? stream.user.jid.bare : stream.user.jid) &&
36
- @config.allowed?(jid, from)
37
- end
24
+ return [] unless @config.allowed?(jid, from)
25
+
26
+ local = @clients[jid.bare] || EMPTY
27
+ local = local.select {|stream| stream.user.jid == jid } unless jid.bare?
28
+ remote = proxies ? proxies(jid) : EMPTY
29
+ [local, remote].flatten
38
30
  end
39
31
 
40
- # Returns streams for all available resources for this JID. A
41
- # resource is marked available after it sends initial presence.
42
- # This method accepts a single JID or a list of JIDs.
32
+ # Returns streams for all available resources for this JID. A resource is
33
+ # marked available after it sends initial presence.
43
34
  def available_resources(*jids, from)
44
- jids = filter_allowed(jids, from)
45
- clients.select do |stream|
46
- stream.available? && jids.include?(stream.user.jid.bare)
35
+ clients(jids, from) do |stream|
36
+ stream.available?
47
37
  end
48
38
  end
49
39
 
50
- # Returns streams for all interested resources for this JID. A
51
- # resource is marked interested after it requests the roster.
52
- # This method accepts a single JID or a list of JIDs.
40
+ # Returns streams for all interested resources for this JID. A resource is
41
+ # marked interested after it requests the roster.
53
42
  def interested_resources(*jids, from)
54
- jids = filter_allowed(jids, from)
55
- clients.select do |stream|
56
- stream.interested? && jids.include?(stream.user.jid.bare)
43
+ clients(jids, from) do |stream|
44
+ stream.interested?
57
45
  end
58
46
  end
59
47
 
60
48
  # Add the connection to the routing table. The connection must return
61
49
  # :client, :server, or :component from its +stream_type+ method so the
62
50
  # router can properly route stanzas to the stream.
63
- def <<(connection)
64
- type = stream_type(connection)
65
- @config ||= connection.config
66
- @streams[type] << connection
51
+ def <<(stream)
52
+ case stream_type(stream)
53
+ when :client then
54
+ return unless stream.connected?
55
+ jid = stream.user.jid.bare
56
+ @clients[jid] ||= []
57
+ @clients[jid] << stream
58
+ when :server then @servers << stream
59
+ when :component then @components << stream
60
+ end
67
61
  end
68
62
 
69
63
  # Remove the connection from the routing table.
70
- def delete(connection)
71
- type = stream_type(connection)
72
- @streams[type].delete(connection)
64
+ def delete(stream)
65
+ case stream_type(stream)
66
+ when :client then
67
+ return unless stream.connected?
68
+ jid = stream.user.jid.bare
69
+ streams = @clients[jid] || []
70
+ streams.delete(stream)
71
+ @clients.delete(jid) if streams.empty?
72
+ when :server then @servers.delete(stream)
73
+ when :component then @components.delete(stream)
74
+ end
73
75
  end
74
76
 
75
77
  # Send the stanza to the appropriate remote server-to-server stream
@@ -96,7 +98,8 @@ module Vines
96
98
 
97
99
  # Returns the total number of streams connected to the server.
98
100
  def size
99
- @streams.values.inject(0) {|sum, arr| sum + arr.size }
101
+ clients = @clients.values.inject(0) {|sum, arr| sum + arr.size }
102
+ clients + @servers.size + @components.size
100
103
  end
101
104
 
102
105
  private
@@ -124,16 +127,27 @@ module Vines
124
127
  end
125
128
  end
126
129
 
127
- # Return the bare JID's from the list that are allowed to talk to
128
- # the +from+ JID. Store them in a Hash for fast +include?+ checks.
130
+ # Return the client streams to which the from address is allowed to
131
+ # contact. Apply the filter block to each stream to narrow the results
132
+ # before returning the streams.
133
+ def clients(jids, from, &filter)
134
+ jids = filter_allowed(jids, from)
135
+ local = @clients.values_at(*jids).compact.flatten.select(&filter)
136
+ proxies = proxies(*jids).select(&filter)
137
+ [local, proxies].flatten
138
+ end
139
+
140
+ # Return the bare JIDs from the list that are allowed to talk to
141
+ # the +from+ JID.
129
142
  def filter_allowed(jids, from)
130
143
  from = JID.new(from)
131
- {}.tap do |ids|
132
- jids.flatten.each do |jid|
133
- jid = JID.new(jid).bare
134
- ids[jid] = nil if @config.allowed?(jid, from)
135
- end
136
- end
144
+ jids.flatten.map {|jid| JID.new(jid).bare }
145
+ .select {|jid| @config.allowed?(jid, from) }
146
+ end
147
+
148
+ def proxies(*jids)
149
+ return EMPTY unless @config.cluster?
150
+ @config.cluster.remote_sessions(*jids)
137
151
  end
138
152
 
139
153
  def connection_to(to, from)
@@ -141,17 +155,17 @@ module Vines
141
155
  end
142
156
 
143
157
  def component_stream(to)
144
- components.find do |stream|
158
+ @components.select do |stream|
145
159
  stream.ready? && stream.remote_domain == to.domain
146
- end
160
+ end.sample
147
161
  end
148
162
 
149
163
  def server_stream(to, from)
150
- servers.find do |stream|
164
+ @servers.select do |stream|
151
165
  stream.ready? &&
152
166
  stream.remote_domain == to.domain &&
153
167
  stream.domain == from.domain
154
- end
168
+ end.sample
155
169
  end
156
170
 
157
171
  def stream_type(connection)
@@ -9,23 +9,36 @@ module Vines
9
9
  register "/iq[@id and @type='get']/ns:query", 'ns' => NS
10
10
 
11
11
  def process
12
- return if route_iq
12
+ return if route_iq || !allowed?
13
13
  result = to_result.tap do |el|
14
14
  el << el.document.create_element('query') do |query|
15
15
  query.default_namespace = NS
16
- query << el.document.create_element('identity', 'category' => 'server', 'type' => 'im')
17
- query << el.document.create_element('feature', 'var' => NAMESPACES[:disco_info])
18
- query << el.document.create_element('feature', 'var' => NAMESPACES[:disco_items])
19
- query << el.document.create_element('feature', 'var' => NAMESPACES[:ping])
20
- query << el.document.create_element('feature', 'var' => NAMESPACES[:vcard])
21
- query << el.document.create_element('feature', 'var' => NAMESPACES[:version])
22
- if stream.config.private_storage?(stream.domain)
23
- query << el.document.create_element('feature', 'var' => NAMESPACES[:storage])
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)
24
25
  end
25
26
  end
26
27
  end
27
28
  stream.write(result)
28
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
29
42
  end
30
43
  end
31
44
  end
@@ -9,12 +9,15 @@ module Vines
9
9
  register "/iq[@id and @type='get']/ns:query", 'ns' => NS
10
10
 
11
11
  def process
12
- return if route_iq
12
+ return if route_iq || !allowed?
13
13
  result = to_result.tap do |el|
14
14
  el << el.document.create_element('query') do |query|
15
15
  query.default_namespace = NS
16
- stream.config.vhosts[stream.domain].components.keys.sort.each do |domain|
17
- query << el.document.create_element('item', 'jid' => domain)
16
+ unless to_pubsub_domain?
17
+ to = (validate_to || stream.domain).to_s
18
+ stream.config.vhosts[to].disco_items.each do |domain|
19
+ query << el.document.create_element('item', 'jid' => domain)
20
+ end
18
21
  end
19
22
  end
20
23
  end
@@ -7,7 +7,7 @@ module Vines
7
7
  register "/iq[@id and @type='get']/ns:ping", 'ns' => NAMESPACES[:ping]
8
8
 
9
9
  def process
10
- return if route_iq
10
+ return if route_iq || !allowed?
11
11
  stream.write(to_result)
12
12
  end
13
13
  end
@@ -44,14 +44,10 @@ module Vines
44
44
  private
45
45
 
46
46
  def to_result
47
- doc = Document.new
48
- node = doc.create_element('iq',
49
- 'from' => stream.user.jid.to_s,
50
- 'id' => self['id'],
51
- 'to' => stream.user.jid.to_s,
52
- 'type' => 'result')
53
- yield node if block_given?
54
- node
47
+ super.tap do |node|
48
+ node['from'] = stream.user.jid.to_s
49
+ yield node if block_given?
50
+ end
55
51
  end
56
52
 
57
53
  def validate_children_size
@@ -55,7 +55,7 @@ module Vines
55
55
 
56
56
  contact = stream.user.contact(jid)
57
57
  unless contact
58
- contact = Contact.new(:jid => jid)
58
+ contact = Contact.new(jid: jid)
59
59
  stream.user.roster << contact
60
60
  end
61
61
  contact.name = item['name']
@@ -89,9 +89,8 @@ module Vines
89
89
  end
90
90
 
91
91
  send_result_iq
92
- push_roster_updates(stream.user.jid, Contact.new(
93
- :jid => contact.jid,
94
- :subscription => 'remove'))
92
+ push_roster_updates(stream.user.jid,
93
+ Contact.new(jid: contact.jid, subscription: 'remove'))
95
94
 
96
95
  if local_jid?(contact.jid)
97
96
  send_unavailable(stream.user.jid, contact.jid) if contact.subscribed_from?
@@ -110,14 +109,7 @@ module Vines
110
109
  presence = [%w[to unsubscribe], %w[from unsubscribed]].map do |meth, type|
111
110
  presence(contact.jid, type) if contact.send("subscribed_#{meth}?")
112
111
  end.compact
113
-
114
- if local_jid?(contact.jid)
115
- stream.interested_resources(contact.jid).each do |recipient|
116
- presence.each {|el| recipient.write(el) }
117
- end
118
- else
119
- presence.each {|el| router.route(el) }
120
- end
112
+ broadcast_to_interested_resources(presence, contact.jid)
121
113
  end
122
114
 
123
115
  def presence(to, type)
@@ -138,8 +130,8 @@ module Vines
138
130
  end
139
131
 
140
132
  def send_result_iq
141
- doc = Document.new
142
- node = doc.create_element('iq', 'id' => self['id'], 'type' => 'result')
133
+ node = to_result
134
+ node.remove_attribute('from')
143
135
  stream.write(node)
144
136
  end
145
137
  end
@@ -3,18 +3,13 @@
3
3
  module Vines
4
4
  class Stanza
5
5
  class Iq
6
- # Session support is deprecated, but Adium requires it so reply with an
6
+ # Session support is deprecated, but Adium requires it, so reply with an
7
7
  # iq result stanza.
8
8
  class Session < Iq
9
9
  register "/iq[@id and @type='set']/ns:session", 'ns' => NAMESPACES[:session]
10
10
 
11
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)
12
+ stream.write(to_result)
18
13
  end
19
14
  end
20
15
  end
@@ -9,6 +9,7 @@ module Vines
9
9
  register "/iq[@id and @type='get' or @type='set']/ns:vCard", 'ns' => NS
10
10
 
11
11
  def process
12
+ return unless allowed?
12
13
  if local?
13
14
  get? ? vcard_query : vcard_update
14
15
  else
@@ -22,7 +23,7 @@ module Vines
22
23
  def vcard_query
23
24
  to = validate_to
24
25
  jid = to ? to.bare : stream.user.jid.bare
25
- card = storage.find_vcard(jid)
26
+ card = storage(jid.domain).find_vcard(jid)
26
27
 
27
28
  raise StanzaErrors::ItemNotFound.new(self, 'cancel') unless card
28
29
 
@@ -45,11 +46,8 @@ module Vines
45
46
 
46
47
  storage.save_vcard(stream.user.jid, elements.first)
47
48
 
48
- doc = Document.new
49
- result = doc.create_element('iq',
50
- 'id' => self['id'],
51
- 'to' => stream.user.jid.to_s,
52
- 'type' => 'result')
49
+ result = to_result
50
+ result.remove_attribute('from')
53
51
  stream.write(result)
54
52
  end
55
53
  end
@@ -9,7 +9,7 @@ module Vines
9
9
  register "/iq[@id and @type='get']/ns:query", 'ns' => NS
10
10
 
11
11
  def process
12
- return if route_iq
12
+ return if route_iq || to_pubsub_domain? || !allowed?
13
13
  result = to_result.tap do |node|
14
14
  node << node.document.create_element('query') do |query|
15
15
  query.default_namespace = NS
@@ -24,25 +24,23 @@ module Vines
24
24
  def to_result
25
25
  doc = Document.new
26
26
  doc.create_element('iq',
27
- 'from' => stream.domain,
27
+ 'from' => validate_to || stream.domain,
28
28
  'id' => self['id'],
29
- 'to' => stream.user.jid.to_s,
29
+ 'to' => stream.user.jid,
30
30
  'type' => 'result')
31
31
  end
32
32
 
33
33
  private
34
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.
35
39
  def route_iq
36
40
  to = validate_to
37
- return false if to.nil? || to.to_s == stream.domain
41
+ return false if to.nil? || stream.config.vhost?(to) || to_pubsub_domain?
38
42
  self['from'] = stream.user.jid.to_s
39
- if local?
40
- stream.available_resources(to).each do |recipient|
41
- recipient.write(@node)
42
- end
43
- else
44
- route
45
- end
43
+ local? ? broadcast(stream.connected_resources(to)) : route
46
44
  true
47
45
  end
48
46
  end