vines 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +2 -2
- data/Rakefile +63 -8
- data/bin/vines +0 -1
- data/conf/config.rb +16 -7
- data/lib/vines.rb +21 -16
- data/lib/vines/command/init.rb +5 -3
- data/lib/vines/config.rb +34 -0
- data/lib/vines/contact.rb +14 -0
- data/lib/vines/stanza.rb +26 -0
- data/lib/vines/stanza/iq.rb +1 -1
- data/lib/vines/stanza/iq/disco_info.rb +3 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/roster.rb +26 -30
- data/lib/vines/stanza/presence.rb +0 -12
- data/lib/vines/stanza/presence/subscribe.rb +3 -20
- data/lib/vines/stanza/presence/subscribed.rb +9 -10
- data/lib/vines/stanza/presence/unsubscribe.rb +8 -15
- data/lib/vines/stanza/presence/unsubscribed.rb +8 -8
- data/lib/vines/storage.rb +28 -0
- data/lib/vines/storage/couchdb.rb +29 -0
- data/lib/vines/storage/local.rb +22 -0
- data/lib/vines/storage/redis.rb +26 -0
- data/lib/vines/storage/sql.rb +48 -5
- data/lib/vines/stream/client.rb +6 -8
- data/lib/vines/stream/http.rb +23 -21
- data/lib/vines/stream/http/auth.rb +1 -1
- data/lib/vines/stream/http/bind.rb +1 -1
- data/lib/vines/stream/http/bind_restart.rb +4 -3
- data/lib/vines/stream/http/ready.rb +1 -1
- data/lib/vines/stream/http/request.rb +94 -5
- data/lib/vines/stream/http/session.rb +8 -6
- data/lib/vines/version.rb +1 -1
- data/test/config_test.rb +12 -0
- data/test/contact_test.rb +40 -0
- data/test/rake_test_loader.rb +11 -3
- data/test/stanza/iq/private_storage_test.rb +177 -0
- data/test/stanza/iq/roster_test.rb +1 -1
- data/test/stanza/iq_test.rb +63 -0
- data/test/storage/couchdb_test.rb +7 -1
- data/test/storage/local_test.rb +8 -2
- data/test/storage/redis_test.rb +16 -7
- data/test/storage/sql_test.rb +8 -1
- data/test/storage/storage_tests.rb +50 -0
- data/test/stream/http/auth_test.rb +3 -0
- data/test/stream/http/ready_test.rb +3 -0
- data/test/stream/http/request_test.rb +86 -0
- data/test/stream/parser_test.rb +2 -0
- data/web/404.html +43 -0
- data/web/apple-touch-icon.png +0 -0
- data/web/chat/coffeescripts/chat.coffee +385 -0
- data/web/chat/coffeescripts/init.coffee +15 -0
- data/web/chat/coffeescripts/logout.coffee +5 -0
- data/web/chat/index.html +17 -0
- data/web/chat/javascripts/app.js +1 -0
- data/web/chat/javascripts/chat.js +436 -0
- data/web/chat/javascripts/init.js +21 -0
- data/web/chat/javascripts/logout.js +11 -0
- data/web/chat/stylesheets/chat.css +290 -0
- data/web/favicon.png +0 -0
- data/web/lib/coffeescripts/contact.coffee +32 -0
- data/web/lib/coffeescripts/layout.coffee +30 -0
- data/web/lib/coffeescripts/login.coffee +52 -0
- data/web/lib/coffeescripts/navbar.coffee +84 -0
- data/web/lib/coffeescripts/router.coffee +40 -0
- data/web/lib/coffeescripts/session.coffee +211 -0
- data/web/lib/images/default-user.png +0 -0
- data/web/lib/images/logo-large.png +0 -0
- data/web/lib/images/logo-small.png +0 -0
- data/web/lib/javascripts/base.js +9 -0
- data/web/lib/javascripts/contact.js +94 -0
- data/web/lib/javascripts/icons.js +101 -0
- data/web/lib/javascripts/jquery.cookie.js +91 -0
- data/web/lib/javascripts/jquery.js +18 -0
- data/web/lib/javascripts/layout.js +48 -0
- data/web/lib/javascripts/login.js +61 -0
- data/web/lib/javascripts/navbar.js +69 -0
- data/web/lib/javascripts/raphael.js +8 -0
- data/web/lib/javascripts/router.js +105 -0
- data/web/lib/javascripts/session.js +322 -0
- data/web/lib/javascripts/strophe.js +1 -0
- data/web/lib/stylesheets/base.css +223 -0
- data/web/lib/stylesheets/login.css +63 -0
- metadata +51 -9
data/lib/vines/stream/client.rb
CHANGED
@@ -24,20 +24,18 @@ module Vines
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
%w[max_stanza_size max_resources_per_account private_storage?].each do |name|
|
28
|
+
define_method name do |*args|
|
29
|
+
@config[:client].send(name, *args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
27
33
|
def ssl_handshake_completed
|
28
34
|
if get_peer_cert
|
29
35
|
close_connection unless cert_domain_matches?(@session.domain)
|
30
36
|
end
|
31
37
|
end
|
32
38
|
|
33
|
-
def max_stanza_size
|
34
|
-
@config[:client].max_stanza_size
|
35
|
-
end
|
36
|
-
|
37
|
-
def max_resources_per_account
|
38
|
-
@config[:client].max_resources_per_account
|
39
|
-
end
|
40
|
-
|
41
39
|
def unbind
|
42
40
|
@session.unbind!(self)
|
43
41
|
super
|
data/lib/vines/stream/http.rb
CHANGED
@@ -17,38 +17,40 @@ module Vines
|
|
17
17
|
body = ''
|
18
18
|
p.on_body = proc {|data| body << data }
|
19
19
|
p.on_message_complete = proc {
|
20
|
-
process_request(body)
|
20
|
+
process_request(Request.new(self, @parser, body))
|
21
21
|
body = ''
|
22
22
|
}
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
26
|
+
# If the session ID is valid, switch this stream's session to the new
|
27
|
+
# ID and return true. Some clients, like Google Chrome, reuse one stream
|
28
|
+
# for multiple sessions.
|
29
29
|
def valid_session?(sid)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@config[:http].max_stanza_size
|
30
|
+
if session = Sessions[sid]
|
31
|
+
@session = session
|
32
|
+
end
|
33
|
+
!!session
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
36
|
+
%w[max_stanza_size max_resources_per_account private_storage? bind root].each do |name|
|
37
|
+
define_method name do |*args|
|
38
|
+
@config[:http].send(name, *args)
|
39
|
+
end
|
39
40
|
end
|
40
41
|
|
41
|
-
def process_request(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@session.request(req)
|
42
|
+
def process_request(request)
|
43
|
+
if request.path == self.bind
|
44
|
+
body = Nokogiri::XML(request.body).root
|
45
|
+
if session = Sessions[body['sid']]
|
46
|
+
session.request(request)
|
47
|
+
else
|
48
|
+
@session = Http::Session.new(self)
|
49
|
+
@session.request(request)
|
50
|
+
end
|
51
51
|
@nodes.push(body)
|
52
|
+
else
|
53
|
+
request.reply_with_file(self.root)
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
@@ -9,7 +9,7 @@ module Vines
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def node(node)
|
12
|
-
unless
|
12
|
+
unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
|
13
13
|
raise StreamErrors::NotAuthorized
|
14
14
|
end
|
15
15
|
nodes = stream.parse_body(node)
|
@@ -11,7 +11,7 @@ module Vines
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def node(node)
|
14
|
-
unless
|
14
|
+
unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
|
15
15
|
raise StreamErrors::NotAuthorized
|
16
16
|
end
|
17
17
|
nodes = stream.parse_body(node)
|
@@ -9,7 +9,7 @@ module Vines
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def node(node)
|
12
|
-
raise StreamErrors::NotAuthorized unless
|
12
|
+
raise StreamErrors::NotAuthorized unless restart?(node)
|
13
13
|
|
14
14
|
doc = Document.new
|
15
15
|
body = doc.create_element('body') do |el|
|
@@ -26,9 +26,10 @@ module Vines
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def restart?(node)
|
29
|
+
session = stream.valid_session?(node['sid'])
|
29
30
|
restart = node.attribute_with_ns('restart', NAMESPACES[:bosh]).value rescue nil
|
30
|
-
domain
|
31
|
-
domain && restart == 'true' && node['rid']
|
31
|
+
domain = node['to'] == stream.domain
|
32
|
+
session && body?(node) && domain && restart == 'true' && node['rid']
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -7,7 +7,7 @@ module Vines
|
|
7
7
|
RID, SID, TYPE, TERMINATE = %w[rid sid type terminate].map {|s| s.freeze }
|
8
8
|
|
9
9
|
def node(node)
|
10
|
-
unless
|
10
|
+
unless stream.valid_session?(node[SID]) && body?(node) && node[RID]
|
11
11
|
raise StreamErrors::NotAuthorized
|
12
12
|
end
|
13
13
|
stream.parse_body(node).each do |child|
|
@@ -4,10 +4,32 @@ module Vines
|
|
4
4
|
class Stream
|
5
5
|
class Http
|
6
6
|
class Request
|
7
|
-
|
7
|
+
BUF_SIZE = 1024
|
8
|
+
MODIFIED = '%a, %d %b %Y %H:%M:%S GMT'.freeze
|
9
|
+
NOT_FOUND = 'Not Found'.freeze
|
10
|
+
NOT_MODIFIED = 'Not Modified'.freeze
|
11
|
+
IF_MODIFIED = 'If-Modified-Since'.freeze
|
12
|
+
TEXT_PLAIN = 'text/plain'.freeze
|
13
|
+
CONTENT_TYPES = {
|
14
|
+
'html' => 'text/html; charset="utf-8"',
|
15
|
+
'js' => 'application/javascript; charset="utf-8"',
|
16
|
+
'css' => 'text/css',
|
17
|
+
'png' => 'image/png',
|
18
|
+
'jpg' => 'image/jpeg',
|
19
|
+
'jpeg' => 'image/jpeg',
|
20
|
+
'gif' => 'image/gif',
|
21
|
+
'manifest' => 'text/cache-manifest'
|
22
|
+
}.freeze
|
8
23
|
|
9
|
-
|
10
|
-
|
24
|
+
attr_reader :stream, :body, :headers, :method, :path, :url, :query
|
25
|
+
|
26
|
+
def initialize(stream, parser, body)
|
27
|
+
@stream, @body = stream, body
|
28
|
+
@headers = parser.headers
|
29
|
+
@method = parser.http_method
|
30
|
+
@path = parser.request_path
|
31
|
+
@url = parser.request_url
|
32
|
+
@query = parser.query_string
|
11
33
|
@received = Time.now
|
12
34
|
end
|
13
35
|
|
@@ -16,17 +38,84 @@ module Vines
|
|
16
38
|
Time.now - @received
|
17
39
|
end
|
18
40
|
|
41
|
+
# Write the requested file to the client out of the given document root
|
42
|
+
# directory. Take care to prevent directory traversal attacks with paths
|
43
|
+
# like ../../../etc/passwd. Use the If-Modified-Since request header
|
44
|
+
# to implement caching.
|
45
|
+
def reply_with_file(dir)
|
46
|
+
path = File.expand_path(File.join(dir, @path))
|
47
|
+
path = File.join(path, 'index.html') if File.directory?(path)
|
48
|
+
|
49
|
+
if path.start_with?(dir) && File.exist?(path)
|
50
|
+
modified?(path) ? send_file(path) : send_status(304, NOT_MODIFIED)
|
51
|
+
else
|
52
|
+
missing = File.join(dir, '404.html')
|
53
|
+
if File.exist?(missing)
|
54
|
+
send_file(missing, 404, NOT_FOUND)
|
55
|
+
else
|
56
|
+
send_status(404, NOT_FOUND)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
19
61
|
# Send an HTTP 200 OK response wrapping the XMPP node content back
|
20
62
|
# to the client.
|
21
|
-
def reply(node)
|
63
|
+
def reply(node, content_type)
|
22
64
|
body = node.to_s
|
23
65
|
header = [
|
24
66
|
"HTTP/1.1 200 OK",
|
25
|
-
"Content-Type: #{
|
67
|
+
"Content-Type: #{content_type}",
|
26
68
|
"Content-Length: #{body.bytesize}"
|
27
69
|
].join("\r\n")
|
28
70
|
@stream.stream_write([header, body].join("\r\n\r\n"))
|
29
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Return true if the file has been modified since the client last
|
76
|
+
# requested it with the If-Modified-Since header.
|
77
|
+
def modified?(path)
|
78
|
+
@headers[IF_MODIFIED] != mtime(path)
|
79
|
+
end
|
80
|
+
|
81
|
+
def mtime(path)
|
82
|
+
File.mtime(path).utc.strftime(MODIFIED)
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_status(status, message)
|
86
|
+
header = [
|
87
|
+
"HTTP/1.1 #{status} #{message}",
|
88
|
+
"Connection: close"
|
89
|
+
].join("\r\n")
|
90
|
+
@stream.stream_write("#{header}\r\n\r\n")
|
91
|
+
@stream.close_connection_after_writing
|
92
|
+
end
|
93
|
+
|
94
|
+
# Stream the contents of the file to the client in a 200 OK response.
|
95
|
+
# Send a Last-Modified response header so clients can send us an
|
96
|
+
# If-Modified-Since request header for caching.
|
97
|
+
def send_file(path, status=200, message='OK')
|
98
|
+
header = [
|
99
|
+
"HTTP/1.1 #{status} #{message}",
|
100
|
+
"Connection: close",
|
101
|
+
"Content-Type: #{content_type(path)}",
|
102
|
+
"Content-Length: #{File.size(path)}",
|
103
|
+
"Last-Modified: #{mtime(path)}"
|
104
|
+
].join("\r\n")
|
105
|
+
@stream.stream_write("#{header}\r\n\r\n")
|
106
|
+
|
107
|
+
File.open(path) do |file|
|
108
|
+
while (buf = file.read(BUF_SIZE)) != nil
|
109
|
+
@stream.stream_write(buf)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
@stream.close_connection_after_writing
|
113
|
+
end
|
114
|
+
|
115
|
+
def content_type(path)
|
116
|
+
ext = File.extname(path).sub('.', '')
|
117
|
+
CONTENT_TYPES[ext] || TEXT_PLAIN
|
118
|
+
end
|
30
119
|
end
|
31
120
|
end
|
32
121
|
end
|
@@ -57,12 +57,12 @@ module Vines
|
|
57
57
|
|
58
58
|
def request(request)
|
59
59
|
if @responses.any?
|
60
|
-
request.reply(wrap_body(@responses.join))
|
60
|
+
request.reply(wrap_body(@responses.join), @content_type)
|
61
61
|
@replied = Time.now
|
62
62
|
@responses.clear
|
63
63
|
else
|
64
64
|
while @requests.size >= @hold
|
65
|
-
@requests.shift.reply(wrap_body(''))
|
65
|
+
@requests.shift.reply(wrap_body(''), @content_type)
|
66
66
|
@replied = Time.now
|
67
67
|
end
|
68
68
|
@requests << request
|
@@ -72,8 +72,10 @@ module Vines
|
|
72
72
|
# Send an HTTP 200 OK response wrapping the XMPP node content back
|
73
73
|
# to the client.
|
74
74
|
def reply(node)
|
75
|
-
@requests.shift
|
76
|
-
|
75
|
+
if request = @requests.shift
|
76
|
+
request.reply(node, @content_type)
|
77
|
+
@replied = Time.now
|
78
|
+
end
|
77
79
|
end
|
78
80
|
|
79
81
|
# Write the XMPP node to the client stream after wrapping it in a BOSH
|
@@ -81,7 +83,7 @@ module Vines
|
|
81
83
|
# immediately. If not, it's queued until the next request arrives.
|
82
84
|
def write(node)
|
83
85
|
if request = @requests.shift
|
84
|
-
request.reply(wrap_body(node))
|
86
|
+
request.reply(wrap_body(node), @content_type)
|
85
87
|
@replied = Time.now
|
86
88
|
else
|
87
89
|
@responses << node.to_s
|
@@ -97,7 +99,7 @@ module Vines
|
|
97
99
|
def respond_to_expired_requests
|
98
100
|
expired = @requests.select {|req| req.age > @wait }
|
99
101
|
expired.each do |request|
|
100
|
-
request.reply(wrap_body(''))
|
102
|
+
request.reply(wrap_body(''), @content_type)
|
101
103
|
@requests.delete(request)
|
102
104
|
@replied = Time.now
|
103
105
|
end
|
data/lib/vines/version.rb
CHANGED
data/test/config_test.rb
CHANGED
@@ -205,6 +205,7 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
205
205
|
assert_equal 5222, port.port
|
206
206
|
assert_equal 131_072, port.max_stanza_size
|
207
207
|
assert_equal 5, port.max_resources_per_account
|
208
|
+
refute port.private_storage?
|
208
209
|
assert_equal Vines::Stream::Client, port.stream
|
209
210
|
assert_same config, port.config
|
210
211
|
assert_equal 1, config.ports.size
|
@@ -216,6 +217,7 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
216
217
|
storage(:fs) { dir '.' }
|
217
218
|
end
|
218
219
|
client '0.0.0.1', 42 do
|
220
|
+
private_storage true
|
219
221
|
max_stanza_size 60_000
|
220
222
|
max_resources_per_account 1
|
221
223
|
end
|
@@ -227,6 +229,7 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
227
229
|
assert_equal 42, port.port
|
228
230
|
assert_equal 60_000, port.max_stanza_size
|
229
231
|
assert_equal 1, port.max_resources_per_account
|
232
|
+
assert port.private_storage?
|
230
233
|
assert_equal Vines::Stream::Client, port.stream
|
231
234
|
assert_same config, port.config
|
232
235
|
assert_equal 1, config.ports.size
|
@@ -301,6 +304,9 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
301
304
|
assert_equal 5280, port.port
|
302
305
|
assert_equal 131_072, port.max_stanza_size
|
303
306
|
assert_equal 5, port.max_resources_per_account
|
307
|
+
assert_equal File.join(Dir.pwd, 'web'), port.root
|
308
|
+
assert_equal '/xmpp', port.bind
|
309
|
+
refute port.private_storage?
|
304
310
|
assert_equal Vines::Stream::Http, port.stream
|
305
311
|
assert_same config, port.config
|
306
312
|
assert_equal 1, config.ports.size
|
@@ -312,8 +318,11 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
312
318
|
storage(:fs) { dir '.' }
|
313
319
|
end
|
314
320
|
http '0.0.0.1', 42 do
|
321
|
+
bind '/custom'
|
322
|
+
private_storage true
|
315
323
|
max_stanza_size 60_000
|
316
324
|
max_resources_per_account 1
|
325
|
+
root '/var/www/html'
|
317
326
|
end
|
318
327
|
end
|
319
328
|
port = config.ports.first
|
@@ -323,6 +332,9 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
323
332
|
assert_equal 42, port.port
|
324
333
|
assert_equal 60_000, port.max_stanza_size
|
325
334
|
assert_equal 1, port.max_resources_per_account
|
335
|
+
assert_equal '/var/www/html', port.root
|
336
|
+
assert_equal '/custom', port.bind
|
337
|
+
assert port.private_storage?
|
326
338
|
assert_equal Vines::Stream::Http, port.stream
|
327
339
|
assert_same config, port.config
|
328
340
|
assert_equal 1, config.ports.size
|
data/test/contact_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'vines'
|
4
|
+
require 'ext/nokogiri'
|
4
5
|
require 'minitest/autorun'
|
5
6
|
|
6
7
|
class ContactTest < MiniTest::Unit::TestCase
|
@@ -39,4 +40,43 @@ class ContactTest < MiniTest::Unit::TestCase
|
|
39
40
|
|
40
41
|
assert_equal expected, contact.to_roster_xml.to_xml(:indent => 0).gsub(/\n/, '')
|
41
42
|
end
|
43
|
+
|
44
|
+
def test_send_roster_push
|
45
|
+
contact = Vines::Contact.new(
|
46
|
+
:jid => 'alice@wonderland.lit',
|
47
|
+
:name => "Alice",
|
48
|
+
:groups => %w[Friends Buddies],
|
49
|
+
:subscription => 'from')
|
50
|
+
|
51
|
+
recipient = MiniTest::Mock.new
|
52
|
+
recipient.expect(:user, Vines::User.new(:jid => 'hatter@wonderland.lit'))
|
53
|
+
def recipient.nodes; @nodes; end
|
54
|
+
def recipient.write(node)
|
55
|
+
@nodes ||= []
|
56
|
+
@nodes << node
|
57
|
+
end
|
58
|
+
|
59
|
+
contact.send_roster_push(recipient)
|
60
|
+
assert recipient.verify
|
61
|
+
assert_equal 1, recipient.nodes.size
|
62
|
+
|
63
|
+
expected = node(%q{
|
64
|
+
<iq to="hatter@wonderland.lit" type="set">
|
65
|
+
<query xmlns="jabber:iq:roster">
|
66
|
+
<item jid="alice@wonderland.lit" name="Alice" subscription="from">
|
67
|
+
<group>Buddies</group>
|
68
|
+
<group>Friends</group>
|
69
|
+
</item>
|
70
|
+
</query>
|
71
|
+
</iq>
|
72
|
+
}.strip.gsub(/\n/, '').gsub(/\s{2,}/, ''))
|
73
|
+
recipient.nodes[0].remove_attribute('id') # id is random
|
74
|
+
assert_equal expected, recipient.nodes[0]
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def node(xml)
|
80
|
+
Nokogiri::XML(xml).root
|
81
|
+
end
|
42
82
|
end
|
data/test/rake_test_loader.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
|
-
|
1
|
+
require 'rake'
|
2
2
|
|
3
3
|
# Use the latest MiniTest gem instead of the buggy
|
4
4
|
# version included with Ruby 1.9.2.
|
5
|
-
gem 'minitest'
|
5
|
+
gem 'minitest', '2.2.2'
|
6
6
|
|
7
7
|
# Load the test files from the command line.
|
8
8
|
|
9
|
-
ARGV.each
|
9
|
+
ARGV.each do |f|
|
10
|
+
next if f =~ /^-/
|
11
|
+
|
12
|
+
if f =~ /\*/
|
13
|
+
FileList[f].to_a.each { |fn| require File.expand_path(fn) }
|
14
|
+
else
|
15
|
+
require File.expand_path(f)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'vines'
|
4
|
+
require 'ext/nokogiri'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
|
7
|
+
class PrivateStorageTest < MiniTest::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@stream = MiniTest::Mock.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_feature_disabled_raises_error
|
13
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
|
14
|
+
node = node(%Q{<iq id="42" type="get">#{query}</iq>})
|
15
|
+
|
16
|
+
@stream.expect(:private_storage?, false)
|
17
|
+
|
18
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
19
|
+
assert_raises(Vines::StanzaErrors::ServiceUnavailable) { stanza.process }
|
20
|
+
assert @stream.verify
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_get_another_user_fragment_raises_error
|
24
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
25
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
|
26
|
+
node = node(%Q{<iq id="42" to="hatter@wonderland.lit" type="get">#{query}</iq>})
|
27
|
+
|
28
|
+
@stream.expect(:private_storage?, true)
|
29
|
+
@stream.expect(:user, alice)
|
30
|
+
|
31
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
32
|
+
assert_raises(Vines::StanzaErrors::Forbidden) { stanza.process }
|
33
|
+
assert @stream.verify
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_get_with_zero_children_raises_error
|
37
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
38
|
+
query = %q{<query xmlns="jabber:iq:private"></query>}
|
39
|
+
node = node(%Q{<iq id="42" type="get">#{query}</iq>})
|
40
|
+
|
41
|
+
@stream.expect(:private_storage?, true)
|
42
|
+
|
43
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
44
|
+
assert_raises(Vines::StanzaErrors::NotAcceptable) { stanza.process }
|
45
|
+
assert @stream.verify
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_get_with_two_children_raises_error
|
49
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
50
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/><two xmlns="b"/></query>}
|
51
|
+
node = node(%Q{<iq id="42" type="get">#{query}</iq>})
|
52
|
+
|
53
|
+
@stream.expect(:private_storage?, true)
|
54
|
+
|
55
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
56
|
+
assert_raises(Vines::StanzaErrors::NotAcceptable) { stanza.process }
|
57
|
+
assert @stream.verify
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_set_with_zero_children_raises_error
|
61
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
62
|
+
query = %q{<query xmlns="jabber:iq:private"></query>}
|
63
|
+
node = node(%Q{<iq id="42" type="set">#{query}</iq>})
|
64
|
+
|
65
|
+
@stream.expect(:private_storage?, true)
|
66
|
+
|
67
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
68
|
+
assert_raises(Vines::StanzaErrors::NotAcceptable) { stanza.process }
|
69
|
+
assert @stream.verify
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_get_without_namespace_raises_error
|
73
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
74
|
+
query = %q{<query xmlns="jabber:iq:private"><one/></query>}
|
75
|
+
node = node(%Q{<iq id="42" type="get">#{query}</iq>})
|
76
|
+
|
77
|
+
@stream.expect(:private_storage?, true)
|
78
|
+
|
79
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
80
|
+
assert_raises(Vines::StanzaErrors::NotAcceptable) { stanza.process }
|
81
|
+
assert @stream.verify
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_get_missing_fragment_raises_error
|
85
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
86
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
|
87
|
+
node = node(%Q{<iq id="42" type="get">#{query}</iq>})
|
88
|
+
|
89
|
+
storage = MiniTest::Mock.new
|
90
|
+
storage.expect(:find_fragment, nil, [alice.jid, node.elements[0].elements[0]])
|
91
|
+
|
92
|
+
@stream.expect(:private_storage?, true)
|
93
|
+
@stream.expect(:domain, 'wonderland.lit')
|
94
|
+
@stream.expect(:storage, storage, ['wonderland.lit'])
|
95
|
+
@stream.expect(:user, alice)
|
96
|
+
|
97
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
98
|
+
assert_raises(Vines::StanzaErrors::ItemNotFound) { stanza.process }
|
99
|
+
assert @stream.verify
|
100
|
+
assert storage.verify
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_get_finds_fragment_writes_to_stream
|
104
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
105
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
|
106
|
+
node = node(%Q{<iq id="42" type="get">#{query}</iq>})
|
107
|
+
|
108
|
+
data = %q{<one xmlns="a"><child>data</child></one>}
|
109
|
+
query = %Q{<query xmlns="jabber:iq:private">#{data}</query>}
|
110
|
+
expected = node(%Q{<iq from="#{alice.jid}" id="42" to="#{alice.jid}" type="result">#{query}</iq>})
|
111
|
+
|
112
|
+
storage = MiniTest::Mock.new
|
113
|
+
storage.expect(:find_fragment, node(data), [alice.jid, node.elements[0].elements[0]])
|
114
|
+
|
115
|
+
@stream.expect(:private_storage?, true)
|
116
|
+
@stream.expect(:domain, 'wonderland.lit')
|
117
|
+
@stream.expect(:storage, storage, ['wonderland.lit'])
|
118
|
+
@stream.expect(:user, alice)
|
119
|
+
@stream.expect(:write, nil, [expected])
|
120
|
+
|
121
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
122
|
+
stanza.process
|
123
|
+
assert @stream.verify
|
124
|
+
assert storage.verify
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_set_one_fragment_writes_result_to_stream
|
128
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
129
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/></query>}
|
130
|
+
node = node(%Q{<iq id="42" type="set">#{query}</iq>})
|
131
|
+
|
132
|
+
storage = MiniTest::Mock.new
|
133
|
+
storage.expect(:save_fragment, nil, [alice.jid, node.elements[0].elements[0]])
|
134
|
+
|
135
|
+
expected = node(%Q{<iq from="#{alice.jid}" id="42" to="#{alice.jid}" type="result"/>})
|
136
|
+
|
137
|
+
@stream.expect(:private_storage?, true)
|
138
|
+
@stream.expect(:domain, 'wonderland.lit')
|
139
|
+
@stream.expect(:storage, storage, ['wonderland.lit'])
|
140
|
+
@stream.expect(:user, alice)
|
141
|
+
@stream.expect(:write, nil, [expected])
|
142
|
+
|
143
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
144
|
+
stanza.process
|
145
|
+
assert @stream.verify
|
146
|
+
assert storage.verify
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_set_two_fragments_writes_result_to_stream
|
150
|
+
alice = Vines::User.new(:jid => 'alice@wonderland.lit/tea')
|
151
|
+
query = %q{<query xmlns="jabber:iq:private"><one xmlns="a"/><two xmlns="a"/></query>}
|
152
|
+
node = node(%Q{<iq id="42" type="set">#{query}</iq>})
|
153
|
+
|
154
|
+
storage = MiniTest::Mock.new
|
155
|
+
storage.expect(:save_fragment, nil, [alice.jid, node.elements[0].elements[0]])
|
156
|
+
storage.expect(:save_fragment, nil, [alice.jid, node.elements[0].elements[1]])
|
157
|
+
|
158
|
+
expected = node(%Q{<iq from="#{alice.jid}" id="42" to="#{alice.jid}" type="result"/>})
|
159
|
+
|
160
|
+
@stream.expect(:private_storage?, true)
|
161
|
+
@stream.expect(:domain, 'wonderland.lit')
|
162
|
+
@stream.expect(:storage, storage, ['wonderland.lit'])
|
163
|
+
@stream.expect(:user, alice)
|
164
|
+
@stream.expect(:write, nil, [expected])
|
165
|
+
|
166
|
+
stanza = Vines::Stanza::Iq::PrivateStorage.new(node, @stream)
|
167
|
+
stanza.process
|
168
|
+
assert @stream.verify
|
169
|
+
assert storage.verify
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def node(xml)
|
175
|
+
Nokogiri::XML(xml).root
|
176
|
+
end
|
177
|
+
end
|