vines 0.2.1 → 0.3.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.
- data/README +1 -1
- data/Rakefile +10 -10
- data/conf/certs/ca-bundle.crt +112 -378
- data/conf/config.rb +18 -9
- data/lib/vines.rb +8 -1
- data/lib/vines/command/cert.rb +2 -1
- data/lib/vines/command/init.rb +11 -0
- data/lib/vines/command/ldap.rb +6 -3
- data/lib/vines/command/schema.rb +1 -1
- data/lib/vines/config.rb +57 -146
- data/lib/vines/config/host.rb +85 -0
- data/lib/vines/config/port.rb +111 -0
- data/lib/vines/contact.rb +1 -1
- data/lib/vines/jid.rb +26 -4
- data/lib/vines/kit.rb +6 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +70 -38
- data/lib/vines/stanza.rb +45 -8
- data/lib/vines/stanza/iq.rb +3 -3
- data/lib/vines/stanza/iq/disco_info.rb +5 -1
- data/lib/vines/stanza/iq/disco_items.rb +3 -0
- data/lib/vines/stanza/iq/private_storage.rb +9 -5
- data/lib/vines/stanza/iq/roster.rb +11 -12
- data/lib/vines/stanza/iq/vcard.rb +4 -4
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/message.rb +4 -5
- data/lib/vines/stanza/presence.rb +20 -18
- data/lib/vines/stanza/presence/probe.rb +3 -4
- data/lib/vines/stanza/presence/subscribe.rb +4 -3
- data/lib/vines/stanza/presence/subscribed.rb +6 -5
- data/lib/vines/stanza/presence/unsubscribe.rb +4 -4
- data/lib/vines/stanza/presence/unsubscribed.rb +4 -3
- data/lib/vines/storage/couchdb.rb +3 -3
- data/lib/vines/storage/ldap.rb +19 -8
- data/lib/vines/storage/local.rb +23 -12
- data/lib/vines/storage/redis.rb +3 -3
- data/lib/vines/storage/sql.rb +5 -5
- data/lib/vines/stream.rb +40 -6
- data/lib/vines/stream/client.rb +5 -6
- data/lib/vines/stream/client/auth.rb +3 -2
- data/lib/vines/stream/client/bind.rb +2 -2
- data/lib/vines/stream/client/bind_restart.rb +1 -2
- data/lib/vines/stream/client/ready.rb +2 -0
- data/lib/vines/stream/client/session.rb +13 -4
- data/lib/vines/stream/client/tls.rb +1 -0
- data/lib/vines/stream/component.rb +6 -5
- data/lib/vines/stream/component/ready.rb +5 -6
- data/lib/vines/stream/http.rb +10 -4
- data/lib/vines/stream/http/request.rb +23 -2
- data/lib/vines/stream/server.rb +13 -11
- data/lib/vines/stream/server/outbound/auth_result.rb +1 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +1 -0
- data/lib/vines/stream/server/ready.rb +2 -2
- data/lib/vines/user.rb +2 -1
- data/lib/vines/version.rb +1 -1
- data/test/config/host_test.rb +292 -0
- data/test/config_test.rb +244 -103
- data/test/contact_test.rb +7 -1
- data/test/jid_test.rb +48 -0
- data/test/router_test.rb +16 -47
- data/test/stanza/iq/disco_info_test.rb +76 -0
- data/test/stanza/iq/disco_items_test.rb +47 -0
- data/test/stanza/iq/private_storage_test.rb +33 -10
- data/test/stanza/iq/roster_test.rb +15 -5
- data/test/stanza/iq/vcard_test.rb +8 -25
- data/test/stanza/iq/version_test.rb +62 -0
- data/test/stanza/iq_test.rb +13 -10
- data/test/stanza/message_test.rb +16 -24
- data/test/stanza/presence/probe_test.rb +52 -0
- data/test/stanza/presence/subscribe_test.rb +1 -5
- data/test/stanza_test.rb +77 -0
- data/test/stream/client/auth_test.rb +1 -0
- data/test/stream/client/ready_test.rb +2 -0
- data/test/stream/client/session_test.rb +7 -2
- data/test/stream/component/ready_test.rb +19 -36
- data/test/stream/http/request_test.rb +22 -2
- data/test/stream/server/ready_test.rb +14 -21
- data/web/404.html +9 -3
- data/web/chat/index.html +2 -2
- data/web/chat/javascripts/app.js +1 -1
- data/web/chat/stylesheets/chat.css +4 -9
- data/web/lib/coffeescripts/layout.coffee +2 -2
- data/web/{chat → lib}/coffeescripts/logout.coffee +0 -0
- data/web/lib/coffeescripts/notification.coffee +14 -0
- data/web/lib/coffeescripts/session.coffee +28 -24
- data/web/lib/coffeescripts/transfer.coffee +37 -34
- data/web/lib/javascripts/base.js +8 -8
- data/web/lib/javascripts/icons.js +3 -0
- data/web/lib/javascripts/jquery.js +4 -18
- data/web/lib/javascripts/layout.js +2 -2
- data/web/{chat → lib}/javascripts/logout.js +0 -0
- data/web/lib/javascripts/notification.js +26 -0
- data/web/lib/javascripts/session.js +20 -16
- data/web/lib/javascripts/transfer.js +45 -55
- data/web/lib/stylesheets/base.css +45 -9
- metadata +31 -15
@@ -0,0 +1,111 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Config
|
5
|
+
class Port
|
6
|
+
include Vines::Log
|
7
|
+
|
8
|
+
attr_reader :config, :stream
|
9
|
+
|
10
|
+
%w[host port].each do |name|
|
11
|
+
define_method(name) do
|
12
|
+
@settings[name.to_sym]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(config, host, port, &block)
|
17
|
+
@config, @settings = config, {}
|
18
|
+
instance_eval(&block) if block
|
19
|
+
defaults = {:host => host, :port => port,
|
20
|
+
:max_resources_per_account => 5, :max_stanza_size => 128 * 1024}
|
21
|
+
@settings = defaults.merge(@settings)
|
22
|
+
end
|
23
|
+
|
24
|
+
def max_stanza_size(max=nil)
|
25
|
+
if max
|
26
|
+
# rfc 6120 section 13.12
|
27
|
+
@settings[:max_stanza_size] = [10000, max].max
|
28
|
+
else
|
29
|
+
@settings[:max_stanza_size]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def start
|
34
|
+
type = stream.name.split('::').last.downcase
|
35
|
+
log.info("Accepting #{type} connections on #{host}:#{port}")
|
36
|
+
EventMachine::start_server(host, port, stream, config)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ClientPort < Port
|
41
|
+
def initialize(config, host='0.0.0.0', port=5222, &block)
|
42
|
+
@stream = Vines::Stream::Client
|
43
|
+
super(config, host, port, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def max_resources_per_account(max=nil)
|
47
|
+
if max
|
48
|
+
@settings[:max_resources_per_account] = max
|
49
|
+
else
|
50
|
+
@settings[:max_resources_per_account]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class ServerPort < Port
|
56
|
+
def initialize(config, host='0.0.0.0', port=5269, &block)
|
57
|
+
@hosts, @stream = [], Vines::Stream::Server
|
58
|
+
super(config, host, port, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def hosts(*hosts)
|
62
|
+
if hosts.any?
|
63
|
+
@hosts << hosts
|
64
|
+
@hosts.flatten!
|
65
|
+
else
|
66
|
+
@hosts
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class HttpPort < Port
|
72
|
+
def initialize(config, host='0.0.0.0', port=5280, &block)
|
73
|
+
@stream = Vines::Stream::Http
|
74
|
+
super(config, host, port, &block)
|
75
|
+
defaults = {:root => File.expand_path('web'), :bind => '/xmpp'}
|
76
|
+
@settings = defaults.merge(@settings)
|
77
|
+
end
|
78
|
+
|
79
|
+
def max_resources_per_account(max=nil)
|
80
|
+
if max
|
81
|
+
@settings[:max_resources_per_account] = max
|
82
|
+
else
|
83
|
+
@settings[:max_resources_per_account]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def root(dir=nil)
|
88
|
+
if dir
|
89
|
+
@settings[:root] = File.expand_path(dir)
|
90
|
+
else
|
91
|
+
@settings[:root]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def bind(url=nil)
|
96
|
+
if url
|
97
|
+
@settings[:bind] = url
|
98
|
+
else
|
99
|
+
@settings[:bind]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class ComponentPort < Port
|
105
|
+
def initialize(config, host='0.0.0.0', port=5347, &block)
|
106
|
+
@stream = Vines::Stream::Component
|
107
|
+
super(config, host, port, &block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/vines/contact.rb
CHANGED
@@ -9,7 +9,7 @@ module Vines
|
|
9
9
|
|
10
10
|
def initialize(args={})
|
11
11
|
@jid = JID.new(args[:jid]).bare
|
12
|
-
raise ArgumentError, 'invalid jid'
|
12
|
+
raise ArgumentError, 'invalid jid' if @jid.empty?
|
13
13
|
@name = args[:name]
|
14
14
|
@subscription = args[:subscription] || 'none'
|
15
15
|
@ask = args[:ask]
|
data/lib/vines/jid.rb
CHANGED
@@ -6,6 +6,12 @@ module Vines
|
|
6
6
|
|
7
7
|
PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.freeze
|
8
8
|
|
9
|
+
# http://tools.ietf.org/html/rfc6122#appendix-A
|
10
|
+
NODE_PREP = /[[:space:][:cntrl:]"&'\/:<>@]/.freeze
|
11
|
+
|
12
|
+
# http://tools.ietf.org/html/rfc3454#appendix-C
|
13
|
+
NAME_PREP = /[[:space:][:cntrl:]]/.freeze
|
14
|
+
|
9
15
|
attr_reader :node, :domain, :resource
|
10
16
|
attr_writer :resource
|
11
17
|
|
@@ -19,11 +25,9 @@ module Vines
|
|
19
25
|
if @domain.nil? && @resource.nil?
|
20
26
|
@node, @domain, @resource = @node.to_s.scan(PATTERN).first
|
21
27
|
end
|
22
|
-
[@node, @domain].each {|
|
28
|
+
[@node, @domain].each {|part| part.downcase! if part }
|
23
29
|
|
24
|
-
|
25
|
-
raise ArgumentError, 'jid too long' if (piece || '').size > 1023
|
26
|
-
end
|
30
|
+
validate
|
27
31
|
end
|
28
32
|
|
29
33
|
def bare
|
@@ -34,6 +38,10 @@ module Vines
|
|
34
38
|
@resource.nil?
|
35
39
|
end
|
36
40
|
|
41
|
+
def empty?
|
42
|
+
to_s == ''
|
43
|
+
end
|
44
|
+
|
37
45
|
def <=>(jid)
|
38
46
|
self.to_s <=> jid.to_s
|
39
47
|
end
|
@@ -52,5 +60,19 @@ module Vines
|
|
52
60
|
s = "#{s}/#{@resource}" if @resource
|
53
61
|
s
|
54
62
|
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def validate
|
67
|
+
[@node, @domain, @resource].each do |part|
|
68
|
+
raise ArgumentError, 'jid too long' if (part || '').size > 1023
|
69
|
+
end
|
70
|
+
raise ArgumentError, 'empty node' if @node && @node.strip.empty?
|
71
|
+
raise ArgumentError, 'node contains invalid characters' if @node && @node =~ NODE_PREP
|
72
|
+
raise ArgumentError, 'empty resource' if @resource && @resource.strip.empty?
|
73
|
+
raise ArgumentError, 'resource contains invalid characters' if @resource && @resource =~ NAME_PREP
|
74
|
+
raise ArgumentError, 'empty domain' if @domain == '' && (@node || @resource)
|
75
|
+
raise ArgumentError, 'domain contains invalid characters' if @domain && @domain =~ NAME_PREP
|
76
|
+
end
|
55
77
|
end
|
56
78
|
end
|
data/lib/vines/kit.rb
CHANGED
@@ -19,5 +19,11 @@ module Vines
|
|
19
19
|
hex[16] = %w[8 9 a b][rand(4)]
|
20
20
|
hex.scan(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/).first.join('-')
|
21
21
|
end
|
22
|
+
|
23
|
+
def self.generate_password
|
24
|
+
hash = Digest::SHA512.new
|
25
|
+
1024.times { hash << rand.to_s }
|
26
|
+
hash.hexdigest
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
data/lib/vines/log.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
module Log
|
5
|
+
@@logger = nil
|
6
|
+
def log
|
7
|
+
unless @@logger
|
8
|
+
@@logger = Logger.new(STDOUT)
|
9
|
+
@@logger.level = Logger::INFO
|
10
|
+
@@logger.progname = 'vines'
|
11
|
+
@@logger.formatter = Class.new(Logger::Formatter) do
|
12
|
+
def initialize
|
13
|
+
@time = "%Y-%m-%dT%H:%M:%SZ".freeze
|
14
|
+
@fmt = "[%s] %5s -- %s: %s\n".freeze
|
15
|
+
end
|
16
|
+
def call(severity, time, program, msg)
|
17
|
+
@fmt % [time.utc.strftime(@time), severity, program, msg2str(msg)]
|
18
|
+
end
|
19
|
+
end.new
|
20
|
+
end
|
21
|
+
@@logger
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/vines/router.rb
CHANGED
@@ -6,7 +6,6 @@ module Vines
|
|
6
6
|
# the 'to' attribute. Router is a singleton, shared by all streams, that must
|
7
7
|
# be accessed with +Router.instance+, not +Router.new+.
|
8
8
|
class Router
|
9
|
-
ROUTABLE_STANZAS = %w[message iq presence].freeze
|
10
9
|
|
11
10
|
STREAM_TYPES = [:client, :server, :component].freeze
|
12
11
|
STREAM_TYPES.each do |name|
|
@@ -29,30 +28,32 @@ module Vines
|
|
29
28
|
# Returns streams for all connected resources for this JID. A
|
30
29
|
# resource is considered connected after it has completed authentication
|
31
30
|
# and resource binding.
|
32
|
-
def connected_resources(jid)
|
33
|
-
jid = JID.new(jid)
|
31
|
+
def connected_resources(jid, from)
|
32
|
+
jid, from = JID.new(jid), JID.new(from)
|
34
33
|
clients.select do |stream|
|
35
|
-
stream.connected? &&
|
34
|
+
stream.connected? &&
|
35
|
+
jid == (jid.bare? ? stream.user.jid.bare : stream.user.jid) &&
|
36
|
+
@config.allowed?(jid, from)
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
40
|
# Returns streams for all available resources for this JID. A
|
40
41
|
# resource is marked available after it sends initial presence.
|
41
42
|
# This method accepts a single JID or a list of JIDs.
|
42
|
-
def available_resources(*
|
43
|
-
|
43
|
+
def available_resources(*jids, from)
|
44
|
+
jids = filter_allowed(jids, from)
|
44
45
|
clients.select do |stream|
|
45
|
-
stream.available? &&
|
46
|
+
stream.available? && jids.include?(stream.user.jid.bare)
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
50
|
# Returns streams for all interested resources for this JID. A
|
50
51
|
# resource is marked interested after it requests the roster.
|
51
52
|
# This method accepts a single JID or a list of JIDs.
|
52
|
-
def interested_resources(*
|
53
|
-
|
53
|
+
def interested_resources(*jids, from)
|
54
|
+
jids = filter_allowed(jids, from)
|
54
55
|
clients.select do |stream|
|
55
|
-
stream.interested? &&
|
56
|
+
stream.interested? && jids.include?(stream.user.jid.bare)
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
@@ -75,40 +76,24 @@ module Vines
|
|
75
76
|
# or an external component stream.
|
76
77
|
def route(stanza)
|
77
78
|
to, from = %w[to from].map {|attr| JID.new(stanza[attr]) }
|
78
|
-
|
79
|
+
return unless @config.allowed?(to, from)
|
80
|
+
key = [to.domain, from.domain]
|
81
|
+
|
82
|
+
if stream = connection_to(to, from)
|
79
83
|
stream.write(stanza)
|
80
|
-
elsif @pending.key?(
|
81
|
-
@pending[
|
84
|
+
elsif @pending.key?(key)
|
85
|
+
@pending[key] << stanza
|
82
86
|
elsif @config.s2s?(to.domain)
|
83
|
-
@pending[
|
87
|
+
@pending[key] << stanza
|
84
88
|
Vines::Stream::Server.start(@config, to.domain, from.domain) do |stream|
|
85
|
-
|
86
|
-
|
87
|
-
else
|
88
|
-
@pending[to.domain].each do |s|
|
89
|
-
xml = StanzaErrors::RemoteServerNotFound.new(s, 'cancel').to_xml
|
90
|
-
connected_resources(s['from']).each {|c| c.write(xml) }
|
91
|
-
end
|
92
|
-
end
|
93
|
-
@pending.delete(to.domain)
|
89
|
+
stream ? send_pending(key, stream) : return_pending(key)
|
90
|
+
@pending.delete(key)
|
94
91
|
end
|
95
92
|
else
|
96
93
|
raise StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel')
|
97
94
|
end
|
98
95
|
end
|
99
96
|
|
100
|
-
# Returns true if this stanza should be processed locally. Returns false
|
101
|
-
# if it's destined for a remote domain or external component.
|
102
|
-
def local?(stanza)
|
103
|
-
return true unless ROUTABLE_STANZAS.include?(stanza.name)
|
104
|
-
to = (stanza['to'] || '').strip
|
105
|
-
to.empty? || local_jid?(to)
|
106
|
-
end
|
107
|
-
|
108
|
-
def local_jid?(jid)
|
109
|
-
@config.vhost?(JID.new(jid).domain)
|
110
|
-
end
|
111
|
-
|
112
97
|
# Returns the total number of streams connected to the server.
|
113
98
|
def size
|
114
99
|
@streams.values.inject(0) {|sum, arr| sum + arr.size }
|
@@ -116,9 +101,56 @@ module Vines
|
|
116
101
|
|
117
102
|
private
|
118
103
|
|
119
|
-
|
120
|
-
|
121
|
-
|
104
|
+
# Write all pending stanzas for this domain to the stream. Called after a
|
105
|
+
# s2s stream has successfully connected and we need to dequeue all stanzas
|
106
|
+
# we received while waiting for the connection to finish.
|
107
|
+
def send_pending(key, stream)
|
108
|
+
@pending[key].each do |stanza|
|
109
|
+
stream.write(stanza)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return all pending stanzas to their senders as remote-server-not-found
|
114
|
+
# errors. Called after a s2s stream has failed to connect.
|
115
|
+
def return_pending(key)
|
116
|
+
@pending[key].each do |stanza|
|
117
|
+
to, from = JID.new(stanza['to']), JID.new(stanza['from'])
|
118
|
+
xml = StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel').to_xml
|
119
|
+
if @config.component?(from)
|
120
|
+
connection_to(from, to).write(xml) rescue nil
|
121
|
+
else
|
122
|
+
connected_resources(from, to).each {|c| c.write(xml) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
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.
|
129
|
+
def filter_allowed(jids, from)
|
130
|
+
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
|
137
|
+
end
|
138
|
+
|
139
|
+
def connection_to(to, from)
|
140
|
+
component_stream(to) || server_stream(to, from)
|
141
|
+
end
|
142
|
+
|
143
|
+
def component_stream(to)
|
144
|
+
components.find do |stream|
|
145
|
+
stream.ready? && stream.remote_domain == to.domain
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def server_stream(to, from)
|
150
|
+
servers.find do |stream|
|
151
|
+
stream.ready? &&
|
152
|
+
stream.remote_domain == to.domain &&
|
153
|
+
stream.domain == from.domain
|
122
154
|
end
|
123
155
|
end
|
124
156
|
|
data/lib/vines/stanza.rb
CHANGED
@@ -6,7 +6,13 @@ module Vines
|
|
6
6
|
|
7
7
|
attr_reader :stream
|
8
8
|
|
9
|
+
EMPTY = ''.freeze
|
10
|
+
FROM = 'from'.freeze
|
9
11
|
MESSAGE = 'message'.freeze
|
12
|
+
TO = 'to'.freeze
|
13
|
+
|
14
|
+
ROUTABLE_STANZAS = %w[message iq presence].freeze
|
15
|
+
|
10
16
|
@@types = {}
|
11
17
|
|
12
18
|
def self.register(xpath, ns={})
|
@@ -28,15 +34,23 @@ module Vines
|
|
28
34
|
# Send the stanza to all recipients, stamping it with from and
|
29
35
|
# to addresses first.
|
30
36
|
def broadcast(recipients)
|
31
|
-
@node[
|
37
|
+
@node[FROM] = stream.user.jid.to_s
|
32
38
|
recipients.each do |recipient|
|
33
|
-
@node[
|
39
|
+
@node[TO] = recipient.user.jid.to_s
|
34
40
|
recipient.write(@node)
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
44
|
+
# Returns true if this stanza should be processed locally. Returns false
|
45
|
+
# if it's destined for a remote domain or external component.
|
38
46
|
def local?
|
39
|
-
|
47
|
+
return true unless ROUTABLE_STANZAS.include?(@node.name)
|
48
|
+
to = JID.new(@node['to'])
|
49
|
+
to.empty? || local_jid?(to)
|
50
|
+
end
|
51
|
+
|
52
|
+
def local_jid?(*jids)
|
53
|
+
stream.config.local_jid?(*jids)
|
40
54
|
end
|
41
55
|
|
42
56
|
def route
|
@@ -59,12 +73,12 @@ module Vines
|
|
59
73
|
# recipient's available resources. Route the stanza to a remote server if
|
60
74
|
# the recipient isn't hosted locally.
|
61
75
|
def send_unavailable(from, to)
|
62
|
-
router.available_resources(from)
|
76
|
+
recipients = router.available_resources(to, from) if local_jid?(to)
|
77
|
+
|
78
|
+
router.available_resources(from, to).each do |stream|
|
63
79
|
el = unavailable(stream.user.jid, to)
|
64
|
-
if
|
65
|
-
|
66
|
-
recipient.write(el)
|
67
|
-
end
|
80
|
+
if local_jid?(to)
|
81
|
+
recipients.each {|recipient| recipient.write(el) }
|
68
82
|
else
|
69
83
|
router.route(el)
|
70
84
|
end
|
@@ -81,8 +95,31 @@ module Vines
|
|
81
95
|
'type' => 'unavailable')
|
82
96
|
end
|
83
97
|
|
98
|
+
# Return nil if this stanza has no 'to' attribute. Return a Vines::JID
|
99
|
+
# if it contains a valid 'to' attribute. Raise a JidMalformed error if
|
100
|
+
# the JID is invalid.
|
101
|
+
def validate_to
|
102
|
+
validate_address(TO)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return nil if this stanza has no 'from' attribute. Return a Vines::JID
|
106
|
+
# if it contains a valid 'from' attribute. Raise a JidMalformed error if
|
107
|
+
# the JID is invalid.
|
108
|
+
def validate_from
|
109
|
+
validate_address(FROM)
|
110
|
+
end
|
111
|
+
|
84
112
|
def method_missing(method, *args, &block)
|
85
113
|
@node.send(method, *args, &block)
|
86
114
|
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def validate_address(attr)
|
119
|
+
jid = (self[attr] || EMPTY)
|
120
|
+
return if jid.empty?
|
121
|
+
JID.new(jid) rescue
|
122
|
+
raise StanzaErrors::JidMalformed.new(self, 'modify')
|
123
|
+
end
|
87
124
|
end
|
88
125
|
end
|