vines 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +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
|