lygneo-vines 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +7 -0
- data/Rakefile +23 -0
- data/bin/vines +4 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3895 -0
- data/conf/config.rb +42 -0
- data/lib/vines/cli.rb +132 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/ldap.rb +38 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/config/host.rb +125 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/config.rb +223 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/follower.rb +111 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/message.rb +40 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/presence.rb +141 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/storage/ldap.rb +71 -0
- data/lib/vines/storage/local.rb +139 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +138 -0
- data/lib/vines/storage.rb +239 -0
- data/lib/vines/store.rb +110 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/parser.rb +79 -0
- data/lib/vines/stream/sasl.rb +128 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +6 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/lib/vines.rb +203 -0
- data/test/cluster/publisher_test.rb +57 -0
- data/test/cluster/sessions_test.rb +47 -0
- data/test/cluster/subscriber_test.rb +109 -0
- data/test/config/host_test.rb +369 -0
- data/test/config/pubsub_test.rb +187 -0
- data/test/config_test.rb +732 -0
- data/test/error_test.rb +58 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/follower_test.rb +102 -0
- data/test/jid_test.rb +147 -0
- data/test/kit_test.rb +31 -0
- data/test/router_test.rb +243 -0
- data/test/stanza/iq/disco_info_test.rb +78 -0
- data/test/stanza/iq/disco_items_test.rb +49 -0
- data/test/stanza/iq/private_storage_test.rb +184 -0
- data/test/stanza/iq/roster_test.rb +229 -0
- data/test/stanza/iq/session_test.rb +25 -0
- data/test/stanza/iq/vcard_test.rb +146 -0
- data/test/stanza/iq/version_test.rb +64 -0
- data/test/stanza/iq_test.rb +70 -0
- data/test/stanza/message_test.rb +126 -0
- data/test/stanza/presence/probe_test.rb +50 -0
- data/test/stanza/presence/subscribe_test.rb +83 -0
- data/test/stanza/pubsub/create_test.rb +116 -0
- data/test/stanza/pubsub/delete_test.rb +169 -0
- data/test/stanza/pubsub/publish_test.rb +309 -0
- data/test/stanza/pubsub/subscribe_test.rb +205 -0
- data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
- data/test/stanza_test.rb +85 -0
- data/test/storage/ldap_test.rb +201 -0
- data/test/storage/local_test.rb +59 -0
- data/test/storage/mock_redis.rb +97 -0
- data/test/storage/null_test.rb +29 -0
- data/test/storage/storage_tests.rb +182 -0
- data/test/storage_test.rb +85 -0
- data/test/store_test.rb +130 -0
- data/test/stream/client/auth_test.rb +137 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/client/session_test.rb +27 -0
- data/test/stream/component/handshake_test.rb +52 -0
- data/test/stream/component/ready_test.rb +103 -0
- data/test/stream/component/start_test.rb +39 -0
- data/test/stream/http/auth_test.rb +70 -0
- data/test/stream/http/ready_test.rb +86 -0
- data/test/stream/http/request_test.rb +209 -0
- data/test/stream/http/sessions_test.rb +49 -0
- data/test/stream/http/start_test.rb +50 -0
- data/test/stream/parser_test.rb +122 -0
- data/test/stream/sasl_test.rb +195 -0
- data/test/stream/server/auth_test.rb +61 -0
- data/test/stream/server/outbound/auth_test.rb +75 -0
- data/test/stream/server/ready_test.rb +98 -0
- data/test/test_helper.rb +42 -0
- data/test/token_bucket_test.rb +44 -0
- data/test/user_test.rb +96 -0
- data/vines.gemspec +30 -0
- metadata +387 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
describe Vines::Stream::Http::Ready do
|
6
|
+
subject { Vines::Stream::Http::Ready.new(stream, nil) }
|
7
|
+
let(:stream) { MiniTest::Mock.new }
|
8
|
+
let(:alice) { Vines::User.new(jid: 'alice@wonderland.lit') }
|
9
|
+
let(:hatter) { Vines::User.new(jid: 'hatter@wonderland.lit') }
|
10
|
+
let(:config) do
|
11
|
+
Vines::Config.new do
|
12
|
+
host 'wonderland.lit' do
|
13
|
+
storage(:fs) { dir Dir.tmpdir }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raises when body element is missing" do
|
19
|
+
node = node('<presence type="unavailable"/>')
|
20
|
+
stream.expect :valid_session?, true, [nil]
|
21
|
+
-> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises when namespace is missing" do
|
25
|
+
node = node('<body rid="42" sid="12"/>')
|
26
|
+
stream.expect :valid_session?, true, ['12']
|
27
|
+
-> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises when rid attribute is missing" do
|
31
|
+
node = node('<body xmlns="http://jabber.org/protocol/httpbind" sid="12"/>')
|
32
|
+
stream.expect :valid_session?, true, ['12']
|
33
|
+
-> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises when session id is invalid" do
|
37
|
+
stream.expect :valid_session?, false, ['12']
|
38
|
+
node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
|
39
|
+
-> { subject.node(node) }.must_raise Vines::StreamErrors::NotAuthorized
|
40
|
+
stream.verify
|
41
|
+
end
|
42
|
+
|
43
|
+
it "processes when body element is empty" do
|
44
|
+
node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
|
45
|
+
stream.expect :valid_session?, true, ['12']
|
46
|
+
stream.expect :parse_body, [], [node]
|
47
|
+
subject.node(node)
|
48
|
+
stream.verify
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'when receiving multiple stanzas in one body element' do
|
52
|
+
let(:recipient) { MiniTest::Mock.new }
|
53
|
+
let(:bogus) { node('<message type="bogus">raises stanza error</message>') }
|
54
|
+
let(:ok) { node('<message to="hatter@wonderland.lit">but processes this message</message>') }
|
55
|
+
let(:xml) { node(%Q{<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12">#{bogus}#{ok}</body>}) }
|
56
|
+
let(:raises) { Vines::Stanza.from_node(bogus, stream) }
|
57
|
+
let(:processes) { Vines::Stanza.from_node(ok, stream) }
|
58
|
+
|
59
|
+
before do
|
60
|
+
recipient.expect :user, hatter
|
61
|
+
recipient.expect :write, nil, [Vines::Stanza::Message]
|
62
|
+
|
63
|
+
stream.expect :valid_session?, true, ['12']
|
64
|
+
stream.expect :parse_body, [raises, processes], [xml]
|
65
|
+
stream.expect :error, nil, [Vines::StanzaErrors::BadRequest]
|
66
|
+
stream.expect :config, config
|
67
|
+
stream.expect :user, alice
|
68
|
+
stream.expect :connected_resources, [recipient], [hatter.jid]
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'processes all stanzas' do
|
72
|
+
subject.node(xml)
|
73
|
+
stream.verify
|
74
|
+
recipient.verify
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "terminates the session" do
|
79
|
+
node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12" type="terminate"/>')
|
80
|
+
stream.expect :valid_session?, true, ['12']
|
81
|
+
stream.expect :parse_body, [], [node]
|
82
|
+
stream.expect :terminate, nil
|
83
|
+
subject.node(node)
|
84
|
+
stream.verify
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
describe Vines::Stream::Http::Request do
|
6
|
+
PASSWORD = File.expand_path('../passwords')
|
7
|
+
INDEX = File.expand_path('index.html')
|
8
|
+
|
9
|
+
before do
|
10
|
+
File.open(PASSWORD, 'w') {|f| f.puts '/etc/passwd contents' }
|
11
|
+
File.open(INDEX, 'w') {|f| f.puts 'index.html contents' }
|
12
|
+
|
13
|
+
@stream = MiniTest::Mock.new
|
14
|
+
@parser = MiniTest::Mock.new
|
15
|
+
@parser.expect(:headers, {'Content-Type' => 'text/html', 'Host' => 'wonderland.lit'})
|
16
|
+
@parser.expect(:http_method, 'GET')
|
17
|
+
@parser.expect(:request_path, '/blogs/12')
|
18
|
+
@parser.expect(:request_url, '/blogs/12?ok=true')
|
19
|
+
@parser.expect(:query_string, 'ok=true')
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
File.delete(PASSWORD)
|
24
|
+
File.delete(INDEX)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'initialize' do
|
28
|
+
it 'copies request info from parser' do
|
29
|
+
request = Vines::Stream::Http::Request.new(@stream, @parser, '<html></html>')
|
30
|
+
assert_equal request.headers, {'Content-Type' => 'text/html', 'Host' => 'wonderland.lit'}
|
31
|
+
assert_equal request.method, 'GET'
|
32
|
+
assert_equal request.path, '/blogs/12'
|
33
|
+
assert_equal request.url, '/blogs/12?ok=true'
|
34
|
+
assert_equal request.query, 'ok=true'
|
35
|
+
assert_equal request.body, '<html></html>'
|
36
|
+
assert @stream.verify
|
37
|
+
assert @parser.verify
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'reply_with_file' do
|
42
|
+
it 'returns 404 file not found' do
|
43
|
+
request = Vines::Stream::Http::Request.new(@stream, @parser, '<html></html>')
|
44
|
+
headers = [
|
45
|
+
"HTTP/1.1 404 Not Found",
|
46
|
+
"Content-Length: 0"
|
47
|
+
].join("\r\n")
|
48
|
+
|
49
|
+
@stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
|
50
|
+
|
51
|
+
request.reply_with_file(Dir.pwd)
|
52
|
+
assert @stream.verify
|
53
|
+
assert @parser.verify
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'prevents directory traversal with 404 response' do
|
57
|
+
parser = MiniTest::Mock.new
|
58
|
+
parser.expect(:headers, {'Content-Type' => 'text/html', 'Host' => 'wonderland.lit'})
|
59
|
+
parser.expect(:http_method, 'GET')
|
60
|
+
parser.expect(:request_path, '/../passwords')
|
61
|
+
parser.expect(:request_url, '/../passwords')
|
62
|
+
parser.expect(:query_string, '')
|
63
|
+
|
64
|
+
request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
|
65
|
+
|
66
|
+
headers = [
|
67
|
+
"HTTP/1.1 404 Not Found",
|
68
|
+
"Content-Length: 0"
|
69
|
+
].join("\r\n")
|
70
|
+
|
71
|
+
@stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
|
72
|
+
|
73
|
+
request.reply_with_file(Dir.pwd)
|
74
|
+
assert @stream.verify
|
75
|
+
assert parser.verify
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'serves index.html for directory request' do
|
79
|
+
parser = MiniTest::Mock.new
|
80
|
+
parser.expect(:headers, {'Content-Type' => 'text/html', 'Host' => 'wonderland.lit'})
|
81
|
+
parser.expect(:http_method, 'GET')
|
82
|
+
parser.expect(:request_path, '/')
|
83
|
+
parser.expect(:request_url, '/?ok=true')
|
84
|
+
parser.expect(:query_string, 'ok=true')
|
85
|
+
|
86
|
+
request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
|
87
|
+
|
88
|
+
mtime = File.mtime(INDEX).utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
89
|
+
headers = [
|
90
|
+
"HTTP/1.1 200 OK",
|
91
|
+
'Content-Type: text/html; charset="utf-8"',
|
92
|
+
"Content-Length: 20",
|
93
|
+
"Last-Modified: #{mtime}"
|
94
|
+
].join("\r\n")
|
95
|
+
|
96
|
+
@stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
|
97
|
+
@stream.expect(:stream_write, nil, ["index.html contents\n"])
|
98
|
+
|
99
|
+
request.reply_with_file(Dir.pwd)
|
100
|
+
assert @stream.verify
|
101
|
+
assert parser.verify
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'redirects for missing trailing slash' do
|
105
|
+
parser = MiniTest::Mock.new
|
106
|
+
parser.expect(:headers, {'Content-Type' => 'text/html', 'Host' => 'wonderland.lit'})
|
107
|
+
parser.expect(:http_method, 'GET')
|
108
|
+
parser.expect(:request_path, '/http')
|
109
|
+
parser.expect(:request_url, '/http?ok=true')
|
110
|
+
parser.expect(:query_string, 'ok=true')
|
111
|
+
|
112
|
+
request = Vines::Stream::Http::Request.new(@stream, parser, '<html></html>')
|
113
|
+
|
114
|
+
headers = [
|
115
|
+
"HTTP/1.1 301 Moved Permanently",
|
116
|
+
"Content-Length: 0",
|
117
|
+
"Location: http://wonderland.lit/http/?ok=true"
|
118
|
+
].join("\r\n")
|
119
|
+
|
120
|
+
@stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
|
121
|
+
# so the /http url above will work
|
122
|
+
request.reply_with_file(File.expand_path('../../', __FILE__))
|
123
|
+
assert @stream.verify
|
124
|
+
assert parser.verify
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'reply_to_options' do
|
129
|
+
it 'returns cors headers' do
|
130
|
+
parser = MiniTest::Mock.new
|
131
|
+
parser.expect(:headers, {
|
132
|
+
'Content-Type' => 'text/xml',
|
133
|
+
'Host' => 'wonderland.lit',
|
134
|
+
'Origin' => 'remote.wonderland.lit',
|
135
|
+
'Access-Control-Request-Headers' => 'Content-Type, Origin'})
|
136
|
+
parser.expect(:http_method, 'OPTIONS')
|
137
|
+
parser.expect(:request_path, '/xmpp')
|
138
|
+
parser.expect(:request_url, '/xmpp')
|
139
|
+
parser.expect(:query_string, '')
|
140
|
+
|
141
|
+
request = Vines::Stream::Http::Request.new(@stream, parser, '')
|
142
|
+
|
143
|
+
headers = [
|
144
|
+
"HTTP/1.1 200 OK",
|
145
|
+
"Content-Length: 0",
|
146
|
+
"Access-Control-Allow-Origin: *",
|
147
|
+
"Access-Control-Allow-Methods: POST, GET, OPTIONS",
|
148
|
+
"Access-Control-Allow-Headers: Content-Type, Origin",
|
149
|
+
"Access-Control-Max-Age: 2592000"
|
150
|
+
].join("\r\n")
|
151
|
+
|
152
|
+
@stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n"])
|
153
|
+
request.reply_to_options
|
154
|
+
assert @stream.verify
|
155
|
+
assert parser.verify
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe 'reply' do
|
160
|
+
it 'returns set-cookie header when vroute is defined' do
|
161
|
+
reply_with_cookie('v1')
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'does not return set-cookie header when vroute is undefined' do
|
165
|
+
reply_with_cookie('')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def reply_with_cookie(cookie)
|
172
|
+
config = Vines::Config.new do
|
173
|
+
host 'wonderland.lit' do
|
174
|
+
storage(:fs) { dir Dir.tmpdir }
|
175
|
+
end
|
176
|
+
http '0.0.0.0', 5280 do
|
177
|
+
vroute cookie
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
parser = MiniTest::Mock.new
|
182
|
+
parser.expect(:headers, {
|
183
|
+
'Content-Type' => 'text/xml',
|
184
|
+
'Host' => 'wonderland.lit',
|
185
|
+
'Origin' => 'remote.wonderland.lit'})
|
186
|
+
parser.expect(:http_method, 'POST')
|
187
|
+
parser.expect(:request_path, '/xmpp')
|
188
|
+
parser.expect(:request_url, '/xmpp')
|
189
|
+
parser.expect(:query_string, '')
|
190
|
+
|
191
|
+
request = Vines::Stream::Http::Request.new(@stream, parser, '')
|
192
|
+
message = '<message>hello</message>'
|
193
|
+
|
194
|
+
headers = [
|
195
|
+
"HTTP/1.1 200 OK",
|
196
|
+
"Access-Control-Allow-Origin: *",
|
197
|
+
"Content-Type: application/xml",
|
198
|
+
"Content-Length: 24",
|
199
|
+
]
|
200
|
+
headers << "Set-Cookie: vroute=#{cookie}; path=/; HttpOnly" unless cookie.empty?
|
201
|
+
headers = headers.join("\r\n")
|
202
|
+
|
203
|
+
@stream.expect(:stream_write, nil, ["#{headers}\r\n\r\n#{message}"])
|
204
|
+
@stream.expect(:config, config)
|
205
|
+
request.reply(message, 'application/xml')
|
206
|
+
assert @stream.verify
|
207
|
+
assert parser.verify
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
describe Vines::Stream::Http::Sessions do
|
6
|
+
class MockSessions < Vines::Stream::Http::Sessions
|
7
|
+
def start_timer
|
8
|
+
# do nothing
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@sessions = MockSessions.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_session_add_and_delete
|
17
|
+
session = "session"
|
18
|
+
assert_nil @sessions['42']
|
19
|
+
@sessions['42'] = session
|
20
|
+
assert_equal session, @sessions['42']
|
21
|
+
@sessions.delete('42')
|
22
|
+
assert_nil @sessions['42']
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_access_singleton_through_class_methods
|
26
|
+
session = "session"
|
27
|
+
assert_nil MockSessions['42']
|
28
|
+
MockSessions['42'] = session
|
29
|
+
assert_equal session, MockSessions['42']
|
30
|
+
MockSessions.delete('42')
|
31
|
+
assert_nil MockSessions['42']
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_cleanup
|
35
|
+
live = MiniTest::Mock.new
|
36
|
+
live.expect(:expired?, false)
|
37
|
+
|
38
|
+
dead = MiniTest::Mock.new
|
39
|
+
dead.expect(:expired?, true)
|
40
|
+
dead.expect(:close, nil)
|
41
|
+
|
42
|
+
@sessions['live'] = live
|
43
|
+
@sessions['dead'] = dead
|
44
|
+
|
45
|
+
@sessions.send(:cleanup)
|
46
|
+
assert live.verify
|
47
|
+
assert dead.verify
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
describe Vines::Stream::Http::Start do
|
6
|
+
before do
|
7
|
+
@stream = MiniTest::Mock.new
|
8
|
+
@state = Vines::Stream::Http::Start.new(@stream)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_missing_body_raises_error
|
12
|
+
node = node('<presence type="unavailable"/>')
|
13
|
+
assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_body_with_missing_namespace_raises_error
|
17
|
+
node = node('<body rid="42" sid="12"/>')
|
18
|
+
assert_raises(Vines::StreamErrors::NotAuthorized) { @state.node(node) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_missing_session_starts_stream
|
22
|
+
EM.run do
|
23
|
+
node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="12"/>')
|
24
|
+
@stream.expect(:start, nil, [node])
|
25
|
+
@stream.expect(:advance, nil, [Vines::Stream::Http::Auth.new(@stream)])
|
26
|
+
@state.node(node)
|
27
|
+
assert @stream.verify
|
28
|
+
EM.stop
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_valid_session_resumes_stream
|
33
|
+
EM.run do
|
34
|
+
node = node('<body xmlns="http://jabber.org/protocol/httpbind" rid="42" sid="123"/>')
|
35
|
+
session = MiniTest::Mock.new
|
36
|
+
session.expect(:resume, nil, [@stream, node])
|
37
|
+
Vines::Stream::Http::Sessions['123'] = session
|
38
|
+
@state.node(node)
|
39
|
+
assert @stream.verify
|
40
|
+
assert session.verify
|
41
|
+
EM.stop
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def node(xml)
|
48
|
+
Nokogiri::XML(xml).root
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
describe Vines::Stream::Parser do
|
6
|
+
STREAM_START = '<stream:stream to="wonderland.lit" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams">'.freeze
|
7
|
+
|
8
|
+
before do
|
9
|
+
@events = []
|
10
|
+
@parser = Vines::Stream::Parser.new.tap do |p|
|
11
|
+
p.stream_open {|el| @events << el }
|
12
|
+
p.stream_close { @events << :close }
|
13
|
+
p.stanza {|el| @events << el }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_xpath_to_subclass
|
18
|
+
expected = []
|
19
|
+
stanzas = [
|
20
|
+
['<message></message>', Vines::Stanza::Message],
|
21
|
+
['<presence/>', Vines::Stanza::Presence],
|
22
|
+
['<presence type="bogus"/>', Vines::Stanza::Presence],
|
23
|
+
['<presence type="error"/>', Vines::Stanza::Presence::Error],
|
24
|
+
['<presence type="probe"/>', Vines::Stanza::Presence::Probe],
|
25
|
+
['<presence type="subscribe"/>', Vines::Stanza::Presence::Subscribe],
|
26
|
+
['<presence type="subscribed"/>', Vines::Stanza::Presence::Subscribed],
|
27
|
+
['<presence type="unavailable"/>', Vines::Stanza::Presence::Unavailable],
|
28
|
+
['<presence type="unsubscribe"/>', Vines::Stanza::Presence::Unsubscribe],
|
29
|
+
['<presence type="unsubscribed"/>', Vines::Stanza::Presence::Unsubscribed],
|
30
|
+
['<iq id="42" type="get"><query xmlns="http://jabber.org/protocol/disco#info"></query></iq>', Vines::Stanza::Iq::Query::DiscoInfo],
|
31
|
+
['<iq id="42" type="get"><query xmlns="http://jabber.org/protocol/disco#items"></query></iq>', Vines::Stanza::Iq::Query::DiscoItems],
|
32
|
+
['<iq id="42" type="error"></iq>', Vines::Stanza::Iq::Error],
|
33
|
+
['<iq id="42" type="get"><query xmlns="jabber:iq:private"/></iq>', Vines::Stanza::Iq::PrivateStorage],
|
34
|
+
['<iq id="42" type="set"><query xmlns="jabber:iq:private"/></iq>', Vines::Stanza::Iq::PrivateStorage],
|
35
|
+
['<iq id="42" type="get"><ping xmlns="urn:xmpp:ping"/></iq>', Vines::Stanza::Iq::Ping],
|
36
|
+
['<iq id="42" type="result"></iq>', Vines::Stanza::Iq::Result],
|
37
|
+
['<iq id="42" type="get"><query xmlns="jabber:iq:roster"/></iq>', Vines::Stanza::Iq::Query::Roster],
|
38
|
+
['<iq id="42" type="set"><query xmlns="jabber:iq:roster"/></iq>', Vines::Stanza::Iq::Query::Roster],
|
39
|
+
['<iq id="42" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>', Vines::Stanza::Iq::Session],
|
40
|
+
['<iq id="42" type="get"><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq::Vcard],
|
41
|
+
['<iq type="get"><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq],
|
42
|
+
['<iq id="42"><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq],
|
43
|
+
['<iq><vCard xmlns="vcard-temp"/></iq>', Vines::Stanza::Iq],
|
44
|
+
['<bogus/>', NilClass],
|
45
|
+
]
|
46
|
+
@parser << STREAM_START
|
47
|
+
stanzas.each do |stanza, klass|
|
48
|
+
@parser << stanza
|
49
|
+
expected << klass
|
50
|
+
end
|
51
|
+
@parser << '</stream:stream>'
|
52
|
+
assert_equal 'stream', @events.shift.name
|
53
|
+
assert_equal :close, @events.pop
|
54
|
+
assert_equal expected.size, @events.size
|
55
|
+
@events.each_with_index do |ev, ix|
|
56
|
+
assert_equal expected[ix], Vines::Stanza.from_node(ev, nil).class
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_stream_namespace_with_default_prefix
|
61
|
+
@parser << STREAM_START
|
62
|
+
assert_equal 1, @events.size
|
63
|
+
stream = @events.shift
|
64
|
+
assert_equal 'stream', stream.name
|
65
|
+
refute_nil stream.namespace
|
66
|
+
assert_equal 'stream', stream.namespace.prefix
|
67
|
+
assert_equal 'http://etherx.jabber.org/streams', stream.namespace.href
|
68
|
+
expected = {'xmlns' => 'jabber:client', 'xmlns:stream' => 'http://etherx.jabber.org/streams'}
|
69
|
+
assert_equal expected, stream.namespaces
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_stanzas_ignore_default_namespace
|
73
|
+
@parser << STREAM_START
|
74
|
+
@parser << '<message to="alice@wonderland.lit">hello!</message>'
|
75
|
+
assert_equal 2, @events.size
|
76
|
+
@events.shift # discard stream
|
77
|
+
msg = @events.shift
|
78
|
+
assert_equal 'message', msg.name
|
79
|
+
assert msg.namespaces.empty?
|
80
|
+
assert_nil msg.namespace
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_nested_elements_have_namespace
|
84
|
+
@parser << STREAM_START
|
85
|
+
@parser << %q{
|
86
|
+
<iq from='alice@wonderland.lit/tea' id='42' type='set'>
|
87
|
+
<query xmlns='jabber:iq:roster'>
|
88
|
+
<item jid='hatter@wonderland.lit' name='Mad Hatter'>
|
89
|
+
<group>Tea Party</group>
|
90
|
+
</item>
|
91
|
+
</query>
|
92
|
+
</iq>
|
93
|
+
}
|
94
|
+
assert_equal 2, @events.size
|
95
|
+
@events.shift # discard stream
|
96
|
+
iq = @events.shift
|
97
|
+
assert_equal 'iq', iq.name
|
98
|
+
assert iq.namespaces.empty?
|
99
|
+
assert_nil iq.namespace
|
100
|
+
|
101
|
+
query = iq.elements.first
|
102
|
+
refute_nil query.namespace
|
103
|
+
assert_nil query.namespace.prefix
|
104
|
+
assert_equal 'jabber:iq:roster', query.namespace.href
|
105
|
+
expected = {'xmlns' => 'jabber:iq:roster'}
|
106
|
+
assert_equal expected, query.namespaces
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_error_stanzas_have_stream_namespace
|
110
|
+
@parser << STREAM_START
|
111
|
+
@parser << '<stream:error><not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>'
|
112
|
+
assert_equal 2, @events.size
|
113
|
+
@events.shift # discard stream
|
114
|
+
error = @events.shift
|
115
|
+
assert_equal 'error', error.name
|
116
|
+
refute_nil error.namespace
|
117
|
+
assert_equal 'stream', error.namespace.prefix
|
118
|
+
assert_equal 'http://etherx.jabber.org/streams', error.namespace.href
|
119
|
+
expected = {'xmlns:stream' => 'http://etherx.jabber.org/streams'}
|
120
|
+
assert_equal expected, error.namespaces
|
121
|
+
end
|
122
|
+
end
|