vines 0.1.0 → 0.1.1

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