lib/vines/router.rb in vines-0.2.1 vs lib/vines/router.rb in vines-0.3.0

- old
+ new

@@ -4,11 +4,10 @@ # The router tracks all stream connections to the server for all clients, # servers, and components. It sends stanzas to the correct stream based on # the 'to' attribute. Router is a singleton, shared by all streams, that must # be accessed with +Router.instance+, not +Router.new+. class Router - ROUTABLE_STANZAS = %w[message iq presence].freeze STREAM_TYPES = [:client, :server, :component].freeze STREAM_TYPES.each do |name| define_method "#{name}s" do @streams[name] @@ -27,34 +26,36 @@ end # Returns streams for all connected resources for this JID. A # resource is considered connected after it has completed authentication # and resource binding. - def connected_resources(jid) - jid = JID.new(jid) + def connected_resources(jid, from) + jid, from = JID.new(jid), JID.new(from) clients.select do |stream| - stream.connected? && jid == (jid.bare? ? stream.user.jid.bare : stream.user.jid) + stream.connected? && + jid == (jid.bare? ? stream.user.jid.bare : stream.user.jid) && + @config.allowed?(jid, from) end end # Returns streams for all available resources for this JID. A # resource is marked available after it sends initial presence. # This method accepts a single JID or a list of JIDs. - def available_resources(*jid) - ids = jid.flatten.map {|jid| JID.new(jid).bare } + def available_resources(*jids, from) + jids = filter_allowed(jids, from) clients.select do |stream| - stream.available? && ids.include?(stream.user.jid.bare) + stream.available? && jids.include?(stream.user.jid.bare) end end # Returns streams for all interested resources for this JID. A # resource is marked interested after it requests the roster. # This method accepts a single JID or a list of JIDs. - def interested_resources(*jid) - ids = jid.flatten.map {|jid| JID.new(jid).bare } + def interested_resources(*jids, from) + jids = filter_allowed(jids, from) clients.select do |stream| - stream.interested? && ids.include?(stream.user.jid.bare) + stream.interested? && jids.include?(stream.user.jid.bare) end end # Add the connection to the routing table. The connection must return # :client, :server, or :component from its +stream_type+ method so the @@ -73,53 +74,84 @@ # Send the stanza to the appropriate remote server-to-server stream # or an external component stream. def route(stanza) to, from = %w[to from].map {|attr| JID.new(stanza[attr]) } - if stream = connection_to(to.domain) + return unless @config.allowed?(to, from) + key = [to.domain, from.domain] + + if stream = connection_to(to, from) stream.write(stanza) - elsif @pending.key?(to.domain) - @pending[to.domain] << stanza + elsif @pending.key?(key) + @pending[key] << stanza elsif @config.s2s?(to.domain) - @pending[to.domain] << stanza + @pending[key] << stanza Vines::Stream::Server.start(@config, to.domain, from.domain) do |stream| - if stream - @pending[to.domain].each {|s| stream.write(s) } - else - @pending[to.domain].each do |s| - xml = StanzaErrors::RemoteServerNotFound.new(s, 'cancel').to_xml - connected_resources(s['from']).each {|c| c.write(xml) } - end - end - @pending.delete(to.domain) + stream ? send_pending(key, stream) : return_pending(key) + @pending.delete(key) end else raise StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel') end end - # Returns true if this stanza should be processed locally. Returns false - # if it's destined for a remote domain or external component. - def local?(stanza) - return true unless ROUTABLE_STANZAS.include?(stanza.name) - to = (stanza['to'] || '').strip - to.empty? || local_jid?(to) - end - - def local_jid?(jid) - @config.vhost?(JID.new(jid).domain) - end - # Returns the total number of streams connected to the server. def size @streams.values.inject(0) {|sum, arr| sum + arr.size } end private - def connection_to(domain) - (components + servers).find do |stream| - stream.ready? && stream.remote_domain == domain + # Write all pending stanzas for this domain to the stream. Called after a + # s2s stream has successfully connected and we need to dequeue all stanzas + # we received while waiting for the connection to finish. + def send_pending(key, stream) + @pending[key].each do |stanza| + stream.write(stanza) + end + end + + # Return all pending stanzas to their senders as remote-server-not-found + # errors. Called after a s2s stream has failed to connect. + def return_pending(key) + @pending[key].each do |stanza| + to, from = JID.new(stanza['to']), JID.new(stanza['from']) + xml = StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel').to_xml + if @config.component?(from) + connection_to(from, to).write(xml) rescue nil + else + connected_resources(from, to).each {|c| c.write(xml) } + end + end + end + + # Return the bare JID's from the list that are allowed to talk to + # the +from+ JID. Store them in a Hash for fast +include?+ checks. + def filter_allowed(jids, from) + from = JID.new(from) + {}.tap do |ids| + jids.flatten.each do |jid| + jid = JID.new(jid).bare + ids[jid] = nil if @config.allowed?(jid, from) + end + end + end + + def connection_to(to, from) + component_stream(to) || server_stream(to, from) + end + + def component_stream(to) + components.find do |stream| + stream.ready? && stream.remote_domain == to.domain + end + end + + def server_stream(to, from) + servers.find do |stream| + stream.ready? && + stream.remote_domain == to.domain && + stream.domain == from.domain end end def stream_type(connection) connection.stream_type.tap do |type|