vines 0.3.2 → 0.4.0

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