vines 0.1.0 → 0.1.1

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 (66) hide show
  1. data/README +1 -1
  2. data/Rakefile +12 -2
  3. data/conf/config.rb +1 -0
  4. data/lib/vines/config.rb +8 -0
  5. data/lib/vines/contact.rb +2 -4
  6. data/lib/vines/error.rb +1 -1
  7. data/lib/vines/router.rb +26 -18
  8. data/lib/vines/stanza/presence.rb +3 -1
  9. data/lib/vines/stanza.rb +8 -1
  10. data/lib/vines/stream/client/bind.rb +9 -1
  11. data/lib/vines/stream/client/session.rb +146 -0
  12. data/lib/vines/stream/client.rb +19 -78
  13. data/lib/vines/stream/component.rb +6 -2
  14. data/lib/vines/stream/http/auth.rb +22 -0
  15. data/lib/vines/stream/http/bind.rb +32 -0
  16. data/lib/vines/stream/http/bind_restart.rb +36 -0
  17. data/lib/vines/stream/http/ready.rb +25 -0
  18. data/lib/vines/stream/http/request.rb +33 -0
  19. data/lib/vines/stream/http/session.rb +116 -0
  20. data/lib/vines/stream/http/sessions.rb +65 -0
  21. data/lib/vines/stream/http/start.rb +23 -0
  22. data/lib/vines/stream/http.rb +119 -77
  23. data/lib/vines/stream/server.rb +8 -3
  24. data/lib/vines/stream/state.rb +6 -1
  25. data/lib/vines/stream.rb +31 -19
  26. data/lib/vines/user.rb +2 -4
  27. data/lib/vines/version.rb +1 -1
  28. data/lib/vines.rb +10 -4
  29. data/test/config_test.rb +34 -33
  30. data/test/contact_test.rb +42 -0
  31. data/test/error_test.rb +2 -2
  32. data/test/jid_test.rb +7 -7
  33. data/test/kit_test.rb +10 -10
  34. data/test/rake_test_loader.rb +9 -0
  35. data/test/router_test.rb +4 -3
  36. data/test/stanza/iq/roster_test.rb +8 -10
  37. data/test/stanza/iq/session_test.rb +2 -3
  38. data/test/stanza/iq/vcard_test.rb +4 -5
  39. data/test/stanza/message_test.rb +17 -11
  40. data/test/stanza/presence/subscribe_test.rb +3 -4
  41. data/test/storage/couchdb_test.rb +9 -10
  42. data/test/storage/ldap_test.rb +30 -37
  43. data/test/storage/local_test.rb +6 -6
  44. data/test/storage/redis_test.rb +6 -6
  45. data/test/storage/sql_test.rb +5 -5
  46. data/test/storage/storage_tests.rb +11 -11
  47. data/test/storage_test.rb +4 -5
  48. data/test/stream/client/auth_test.rb +15 -16
  49. data/test/stream/client/ready_test.rb +4 -5
  50. data/test/stream/client/session_test.rb +21 -0
  51. data/test/stream/component/handshake_test.rb +6 -7
  52. data/test/stream/component/ready_test.rb +9 -10
  53. data/test/stream/component/start_test.rb +6 -7
  54. data/test/stream/http/auth_test.rb +68 -0
  55. data/test/stream/http/ready_test.rb +56 -0
  56. data/test/stream/http/sessions_test.rb +50 -0
  57. data/test/stream/http/start_test.rb +51 -0
  58. data/test/stream/parser_test.rb +5 -5
  59. data/test/stream/server/outbound/auth_test.rb +12 -13
  60. data/test/stream/server/ready_test.rb +10 -11
  61. data/test/token_bucket_test.rb +7 -7
  62. data/test/user_test.rb +8 -6
  63. metadata +45 -14
  64. data/lib/vines/stream/http/http_request.rb +0 -22
  65. data/lib/vines/stream/http/http_state.rb +0 -139
  66. data/lib/vines/stream/http/http_states.rb +0 -53
data/README CHANGED
@@ -19,7 +19,7 @@ database. SSL encryption is mandatory on all client and server connections.
19
19
  * bcrypt-ruby >= 2.1.4
20
20
  * eventmachine >= 1.0.0
21
21
  * nokogiri >= 1.4.4
22
- * ruby >= 1.9.1
22
+ * ruby >= 1.9.2
23
23
 
24
24
  == Ubuntu setup
25
25
  $ sudo apt-get install build-essential ruby1.9.1 ruby1.9.1-dev libxml2-dev libxslt-dev
data/Rakefile CHANGED
@@ -31,20 +31,30 @@ all client and server connections."
31
31
  s.add_dependency 'em-http-request', '>= 1.0.0.beta.3'
32
32
  s.add_dependency "em-redis", "~> 0.3"
33
33
  s.add_dependency "eventmachine", ">= 1.0.0.beta.3"
34
+ s.add_dependency "http_parser.rb", "~> 0.5"
34
35
  s.add_dependency "net-ldap", "~> 0.2"
35
36
  s.add_dependency "nokogiri", "~> 1.4"
36
- s.add_dependency "thin", "~> 1.2"
37
37
 
38
+ s.add_development_dependency "minitest"
38
39
  s.add_development_dependency "rake"
39
40
  s.add_development_dependency "sqlite3"
40
41
 
41
- s.required_ruby_version = '>= 1.9.1'
42
+ s.required_ruby_version = '>= 1.9.2'
42
43
  end
43
44
 
44
45
  Rake::GemPackageTask.new(spec) do |pkg|
45
46
  pkg.need_tar = true
46
47
  end
47
48
 
49
+ module Rake
50
+ class TestTask
51
+ # use our custom test loader
52
+ def rake_loader
53
+ 'test/rake_test_loader.rb'
54
+ end
55
+ end
56
+ end
57
+
48
58
  Rake::TestTask.new(:test) do |test|
49
59
  test.libs << 'test'
50
60
  test.libs << 'test/storage'
data/conf/config.rb CHANGED
@@ -69,6 +69,7 @@ Vines::Config.configure do
69
69
  # the XMPP server.
70
70
  http '0.0.0.0', 5280 do
71
71
  max_stanza_size 131072
72
+ max_resources_per_account 5
72
73
  end
73
74
 
74
75
  # Configure the XEP-0114 external component port. Add entries for each
data/lib/vines/config.rb CHANGED
@@ -167,6 +167,14 @@ module Vines
167
167
  @stream = Vines::Stream::Http
168
168
  super(config, host, port, &block)
169
169
  end
170
+
171
+ def max_resources_per_account(max=nil)
172
+ if max
173
+ @settings[:max_resources_per_account] = max
174
+ else
175
+ @settings[:max_resources_per_account]
176
+ end
177
+ end
170
178
  end
171
179
 
172
180
  class ComponentPort < Port
data/lib/vines/contact.rb CHANGED
@@ -17,12 +17,10 @@ module Vines
17
17
  end
18
18
 
19
19
  def <=>(contact)
20
- self.jid.to_s <=> contact.jid.to_s
20
+ contact.is_a?(Contact) ? self.jid.to_s <=> contact.jid.to_s : nil
21
21
  end
22
22
 
23
- def eql?(contact)
24
- contact.is_a?(Contact) && self == contact
25
- end
23
+ alias :eql? :==
26
24
 
27
25
  def hash
28
26
  jid.to_s.hash
data/lib/vines/error.rb CHANGED
@@ -98,7 +98,7 @@ module Vines
98
98
  module StreamErrors
99
99
  class BadFormat < StreamError; end
100
100
  class BadNamespacePrefix < StreamError; end
101
- class Confict < StreamError; end
101
+ class Conflict < StreamError; end
102
102
  class ConnectionTimeout < StreamError; end
103
103
  class HostGone < StreamError; end
104
104
  class HostUnknown < StreamError; end
data/lib/vines/router.rb CHANGED
@@ -8,9 +8,16 @@ module Vines
8
8
  class Router
9
9
  ROUTABLE_STANZAS = %w[message iq presence].freeze
10
10
 
11
+ STREAM_TYPES = [:client, :server, :component].freeze
12
+ STREAM_TYPES.each do |name|
13
+ define_method "#{name}s" do
14
+ @streams[name]
15
+ end
16
+ end
17
+
11
18
  @@instance = nil
12
19
  def self.instance
13
- @@instance ||= Router.new
20
+ @@instance ||= self.new
14
21
  end
15
22
 
16
23
  def initialize
@@ -19,23 +26,12 @@ module Vines
19
26
  @pending = Hash.new {|h,k| h[k] = [] }
20
27
  end
21
28
 
22
- %w[Client Server Component].each do |klass|
23
- name = klass.split(/(?=[A-Z])/).join('_').downcase
24
- define_method(name + 's') do
25
- @streams["Vines::Stream::#{klass}"]
26
- end
27
- end
28
-
29
- def http_states
30
- @streams["Vines::Stream::Http::HttpState"]
31
- end
32
-
33
29
  # Returns streams for all connected resources for this JID. A
34
30
  # resource is considered connected after it has completed authentication
35
31
  # and resource binding.
36
32
  def connected_resources(jid)
37
33
  jid = JID.new(jid)
38
- (clients + http_states).select do |stream|
34
+ clients.select do |stream|
39
35
  stream.connected? && jid == (jid.bare? ? stream.user.jid.bare : stream.user.jid)
40
36
  end
41
37
  end
@@ -45,7 +41,7 @@ module Vines
45
41
  # This method accepts a single JID or a list of JIDs.
46
42
  def available_resources(*jid)
47
43
  ids = jid.flatten.map {|jid| JID.new(jid).bare }
48
- (clients + http_states).select do |stream|
44
+ clients.select do |stream|
49
45
  stream.available? && ids.include?(stream.user.jid.bare)
50
46
  end
51
47
  end
@@ -55,20 +51,24 @@ module Vines
55
51
  # This method accepts a single JID or a list of JIDs.
56
52
  def interested_resources(*jid)
57
53
  ids = jid.flatten.map {|jid| JID.new(jid).bare }
58
- (clients + http_states).select do |stream|
54
+ clients.select do |stream|
59
55
  stream.interested? && ids.include?(stream.user.jid.bare)
60
56
  end
61
57
  end
62
58
 
63
- # Add the connection to the routing table.
59
+ # Add the connection to the routing table. The connection must return
60
+ # :client, :server, or :component from its +stream_type+ method so the
61
+ # router can properly route stanzas to the stream.
64
62
  def <<(connection)
63
+ type = stream_type(connection)
65
64
  @config ||= connection.config
66
- @streams[connection.class.to_s] << connection
65
+ @streams[type] << connection
67
66
  end
68
67
 
69
68
  # Remove the connection from the routing table.
70
69
  def delete(connection)
71
- @streams[connection.class.to_s].delete(connection)
70
+ type = stream_type(connection)
71
+ @streams[type].delete(connection)
72
72
  end
73
73
 
74
74
  # Send the stanza to the appropriate remote server-to-server stream
@@ -121,5 +121,13 @@ module Vines
121
121
  stream.ready? && stream.remote_domain == domain
122
122
  end
123
123
  end
124
+
125
+ def stream_type(connection)
126
+ connection.stream_type.tap do |type|
127
+ unless STREAM_TYPES.include?(type)
128
+ raise ArgumentError, "unexpected stream type: #{type}"
129
+ end
130
+ end
131
+ end
124
132
  end
125
133
  end
@@ -40,7 +40,9 @@ module Vines
40
40
  else
41
41
  stream.user.subscribed_from?(to) ? router.available_resources(to) : []
42
42
  end
43
- broadcast(recipients + router.available_resources(stream.user.jid))
43
+
44
+ broadcast(recipients)
45
+ broadcast(router.available_resources(stream.user.jid))
44
46
 
45
47
  if initial
46
48
  stream.available_subscribed_to_resources.each do |recipient|
data/lib/vines/stanza.rb CHANGED
@@ -5,6 +5,7 @@ module Vines
5
5
  include Nokogiri::XML
6
6
 
7
7
  attr_reader :stream
8
+
8
9
  MESSAGE = 'message'.freeze
9
10
  @@types = {}
10
11
 
@@ -24,8 +25,14 @@ module Vines
24
25
  @node, @stream = node, stream
25
26
  end
26
27
 
28
+ # Send the stanza to all recipients, stamping it with from and
29
+ # to addresses first.
27
30
  def broadcast(recipients)
28
- stream.broadcast(@node, recipients)
31
+ @node['from'] = stream.user.jid.to_s
32
+ recipients.each do |recipient|
33
+ @node['to'] = recipient.user.jid.to_s
34
+ recipient.write(@node)
35
+ end
29
36
  end
30
37
 
31
38
  def local?
@@ -25,13 +25,21 @@ module Vines
25
25
  bind << doc.create_element('jid', stream.user.jid.to_s)
26
26
  end
27
27
  end
28
+
28
29
  stream.write(result)
29
- stream.write('<stream:features/>')
30
+ send_empty_features
30
31
  advance
31
32
  end
32
33
 
33
34
  private
34
35
 
36
+ # Write the final <stream:features/> element to the stream, indicating
37
+ # stream negotiation is complete and the client is cleared to send
38
+ # stanzas.
39
+ def send_empty_features
40
+ stream.write('<stream:features/>')
41
+ end
42
+
35
43
  def bind?(node)
36
44
  node.name == 'iq' && node['type'] == 'set' && node.xpath('ns:bind', 'ns' => NS).any?
37
45
  end
@@ -0,0 +1,146 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Client
6
+ # A Session tracks the state of a client stream over its lifetime from
7
+ # negotiation to processing stanzas to shutdown. By disconnecting the
8
+ # stream's state from the stream, we can allow multiple TCP connections
9
+ # to access one logical session (e.g. HTTP streams).
10
+ class Session
11
+ include Comparable
12
+
13
+ attr_accessor :domain, :last_broadcast_presence, :user
14
+ attr_reader :id, :state
15
+
16
+ def initialize(stream)
17
+ @id = Kit.uuid
18
+ @state = Client::Start.new(stream)
19
+ @available = false
20
+ @domain = nil
21
+ @last_broadcast_presence = nil
22
+ @requested_roster = false
23
+ @unbound = false
24
+ @user = nil
25
+ end
26
+
27
+ def <=>(session)
28
+ session.is_a?(Session) ? self.id <=> session.id : nil
29
+ end
30
+
31
+ alias :eql? :==
32
+
33
+ def hash
34
+ @id.hash
35
+ end
36
+
37
+ def advance(state)
38
+ @state = state
39
+ end
40
+
41
+ # Returns true if this client has properly authenticated with
42
+ # the server.
43
+ def authenticated?
44
+ !@user.nil?
45
+ end
46
+
47
+ def available!
48
+ @available = true
49
+ end
50
+
51
+ # An available resource has sent initial presence and can
52
+ # receive presence subscription requests.
53
+ def available?
54
+ @available && connected?
55
+ end
56
+
57
+ # A connected resource has authenticated and bound a resource
58
+ # identifier.
59
+ def connected?
60
+ !@unbound && authenticated? && !@user.jid.bare?
61
+ end
62
+
63
+ # An interested resource has requested its roster and can
64
+ # receive roster pushes.
65
+ def interested?
66
+ @requested_roster && connected?
67
+ end
68
+
69
+ def ready?
70
+ @state.class == Client::Ready
71
+ end
72
+
73
+ def requested_roster!
74
+ @requested_roster = true
75
+ end
76
+
77
+ def stream_type
78
+ :client
79
+ end
80
+
81
+ # Called by the stream when its disconnected from the client. The stream
82
+ # passes itself to this method in case multiple streams are accessing this
83
+ # session.
84
+ def unbind!(stream)
85
+ @unbound = true
86
+ @available = false
87
+ broadcast_unavailable
88
+ end
89
+
90
+ # Returns streams for available resources to which this user
91
+ # has successfully subscribed.
92
+ def available_subscribed_to_resources
93
+ subscribed = @user.subscribed_to_contacts.map {|c| c.jid }
94
+ router.available_resources(subscribed)
95
+ end
96
+
97
+ # Returns streams for available resources that are subscribed
98
+ # to this user's presence updates.
99
+ def available_subscribers
100
+ subscribed = @user.subscribed_from_contacts.map {|c| c.jid }
101
+ router.available_resources(subscribed)
102
+ end
103
+
104
+ # Returns contacts hosted at remote servers that are subscribed
105
+ # to this user's presence updates.
106
+ def remote_subscribers(to=nil)
107
+ jid = (to.nil? || to.empty?) ? nil : JID.new(to).bare
108
+ @user.subscribed_from_contacts.reject do |c|
109
+ router.local_jid?(c.jid) || (jid && c.jid.bare != jid)
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def broadcast_unavailable
116
+ return unless authenticated?
117
+
118
+ doc = Nokogiri::XML::Document.new
119
+ el = doc.create_element('presence',
120
+ 'from' => @user.jid.to_s,
121
+ 'type' => 'unavailable')
122
+
123
+ broadcast(el, available_subscribers)
124
+ broadcast(el, router.available_resources(@user.jid))
125
+
126
+ remote_subscribers.each do |contact|
127
+ node = el.clone
128
+ node['to'] = contact.jid.bare.to_s
129
+ router.route(node) rescue nil # ignore RemoteServerNotFound
130
+ end
131
+ end
132
+
133
+ def broadcast(stanza, recipients)
134
+ recipients.each do |recipient|
135
+ stanza['to'] = recipient.user.jid.to_s
136
+ recipient.write(stanza)
137
+ end
138
+ end
139
+
140
+ def router
141
+ Router.instance
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -6,22 +6,27 @@ module Vines
6
6
  # Implements the XMPP protocol for client-to-server (c2s) streams. This
7
7
  # serves connected streams using the jabber:client namespace.
8
8
  class Client < Stream
9
- attr_reader :config, :domain
10
- attr_accessor :last_broadcast_presence
9
+ attr_reader :config
11
10
 
12
11
  def initialize(config)
13
12
  @config = config
14
- @domain = nil
15
- @requested_roster = false
16
- @available = false
17
- @unbound = false
18
- @last_broadcast_presence = nil
19
- @state = Start.new(self)
13
+ @session = Client::Session.new(self)
14
+ end
15
+
16
+ # Delegate behavior to the session that's storing our stream state.
17
+ def method_missing(name, *args)
18
+ @session.send(name, *args)
19
+ end
20
+
21
+ %w[advance domain state user user=].each do |name|
22
+ define_method name do |*args|
23
+ @session.send(name, *args)
24
+ end
20
25
  end
21
26
 
22
27
  def ssl_handshake_completed
23
28
  if get_peer_cert
24
- close_connection unless cert_domain_matches?(@domain)
29
+ close_connection unless cert_domain_matches?(@session.domain)
25
30
  end
26
31
  end
27
32
 
@@ -34,80 +39,16 @@ module Vines
34
39
  end
35
40
 
36
41
  def unbind
37
- @unbound = true
38
- @available = false
39
- if authenticated?
40
- doc = Nokogiri::XML::Document.new
41
- el = doc.create_element('presence', 'type' => 'unavailable')
42
- Stanza::Presence::Unavailable.new(el, self).outbound_broadcast_presence
43
- end
42
+ @session.unbind!(self)
44
43
  super
45
44
  end
46
45
 
47
- # Returns true if this client has properly authenticated with
48
- # the server.
49
- def authenticated?
50
- !@user.nil?
51
- end
52
-
53
- # A connected resource has authenticated and bound a resource
54
- # identifier.
55
- def connected?
56
- !@unbound && authenticated? && !@user.jid.bare?
57
- end
58
-
59
- # An available resource has sent initial presence and can
60
- # receive presence subscription requests.
61
- def available?
62
- @available && connected?
63
- end
64
-
65
- # An interested resource has requested its roster and can
66
- # receive roster pushes.
67
- def interested?
68
- @requested_roster && connected?
69
- end
70
-
71
- def available!
72
- @available = true
73
- end
74
-
75
- def requested_roster!
76
- @requested_roster = true
77
- end
78
-
79
- # Returns streams for available resources to which this user
80
- # has successfully subscribed.
81
- def available_subscribed_to_resources
82
- subscribed = @user.subscribed_to_contacts.map {|c| c.jid }
83
- router.available_resources(subscribed)
84
- end
85
-
86
- # Returns streams for available resources that are subscribed
87
- # to this user's presence updates.
88
- def available_subscribers
89
- subscribed = @user.subscribed_from_contacts.map {|c| c.jid }
90
- router.available_resources(subscribed)
91
- end
92
-
93
- # Returns contacts hosted at remote servers that are subscribed
94
- # to this user's presence updates.
95
- def remote_subscribers(to=nil)
96
- jid = (to.nil? || to.empty?) ? nil : JID.new(to).bare
97
- @user.subscribed_from_contacts.reject do |c|
98
- router.local_jid?(c.jid) || (jid && c.jid.bare != jid)
99
- end
100
- end
101
-
102
- def ready?
103
- @state.class == Client::Ready
104
- end
105
-
106
46
  def start(node)
107
- @domain, from = %w[to from].map {|a| node[a] }
47
+ to, from = %w[to from].map {|a| node[a] }
48
+ @session.domain = to
108
49
  send_stream_header(from)
109
50
  raise StreamErrors::UnsupportedVersion unless node['version'] == '1.0'
110
- raise StreamErrors::HostUnknown unless @config.vhost?(@domain)
51
+ raise StreamErrors::HostUnknown unless @config.vhost?(@session.domain)
111
52
  raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:client]
112
53
  raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
113
54
  end
@@ -120,7 +61,7 @@ module Vines
120
61
  'xmlns:stream' => NAMESPACES[:stream],
121
62
  'xml:lang' => 'en',
122
63
  'id' => Kit.uuid,
123
- 'from' => @domain,
64
+ 'from' => @session.domain,
124
65
  'version' => '1.0'
125
66
  }
126
67
  attrs['to'] = to if to
@@ -13,7 +13,7 @@ module Vines
13
13
  @config = config
14
14
  @remote_domain = nil
15
15
  @stream_id = Kit.uuid
16
- @state = Start.new(self)
16
+ advance(Start.new(self))
17
17
  end
18
18
 
19
19
  def max_stanza_size
@@ -21,7 +21,11 @@ module Vines
21
21
  end
22
22
 
23
23
  def ready?
24
- @state.class == Component::Ready
24
+ state.class == Component::Ready
25
+ end
26
+
27
+ def stream_type
28
+ :component
25
29
  end
26
30
 
27
31
  def start(node)
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Auth < Client::Auth
7
+ def initialize(stream, success=BindRestart)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ unless body?(node) && node['rid'] && stream.valid_session?(node['sid'])
13
+ raise StreamErrors::NotAuthorized
14
+ end
15
+ nodes = stream.parse_body(node)
16
+ raise StreamErrors::NotAuthorized unless nodes.size == 1
17
+ super(nodes.first)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Bind < Client::Bind
7
+ FEATURES = %Q{<stream:features xmlns:stream="#{NAMESPACES[:stream]}"/>}.freeze
8
+
9
+ def initialize(stream, success=Ready)
10
+ super
11
+ end
12
+
13
+ def node(node)
14
+ unless body?(node) && node['rid'] && stream.valid_session?(node['sid'])
15
+ raise StreamErrors::NotAuthorized
16
+ end
17
+ nodes = stream.parse_body(node)
18
+ raise StreamErrors::NotAuthorized unless nodes.size == 1
19
+ super(nodes.first)
20
+ end
21
+
22
+ private
23
+
24
+ # Override Client::Bind#send_empty_features to properly namespace the
25
+ # empty features element.
26
+ def send_empty_features
27
+ stream.write(FEATURES)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class BindRestart < State
7
+ def initialize(stream, success=Bind)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless body?(node) && restart?(node)
13
+
14
+ doc = Document.new
15
+ body = doc.create_element('body') do |el|
16
+ el.add_namespace(nil, NAMESPACES[:http_bind])
17
+ el.add_namespace('stream', NAMESPACES[:stream])
18
+ el << doc.create_element('stream:features') do |features|
19
+ features << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
20
+ end
21
+ end
22
+ stream.reply(body)
23
+ advance
24
+ end
25
+
26
+ private
27
+
28
+ def restart?(node)
29
+ restart = node.attribute_with_ns('restart', NAMESPACES[:bosh]).value rescue nil
30
+ domain = node['to'] == stream.domain
31
+ domain && restart == 'true' && node['rid'] && stream.valid_session?(node['sid'])
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Ready < Client::Ready
7
+ RID, SID, TYPE, TERMINATE = %w[rid sid type terminate].map {|s| s.freeze }
8
+
9
+ def node(node)
10
+ unless body?(node) && node[RID] && stream.valid_session?(node[SID])
11
+ raise StreamErrors::NotAuthorized
12
+ end
13
+ stream.parse_body(node).each do |child|
14
+ super(child)
15
+ end
16
+ stream.terminate if terminate?(node)
17
+ end
18
+
19
+ def terminate?(node)
20
+ node[TYPE] == TERMINATE
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end