vines 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/Rakefile +33 -53
- data/conf/certs/README +10 -3
- data/conf/certs/ca-bundle.crt +55 -410
- data/lib/vines/cluster/connection.rb +1 -1
- data/lib/vines/command/ldap.rb +1 -1
- data/lib/vines/command/schema.rb +1 -1
- data/lib/vines/config.rb +11 -5
- data/lib/vines/jid.rb +3 -3
- data/lib/vines/stanza/iq/disco_items.rb +1 -1
- data/lib/vines/storage/local.rb +1 -0
- data/lib/vines/store.rb +50 -9
- data/lib/vines/stream.rb +5 -9
- data/lib/vines/stream/client/session.rb +1 -1
- data/lib/vines/stream/http.rb +3 -1
- data/lib/vines/stream/http/ready.rb +5 -1
- data/lib/vines/stream/http/request.rb +22 -0
- data/lib/vines/version.rb +1 -1
- data/test/config/host_test.rb +9 -9
- data/test/config/pubsub_test.rb +2 -2
- data/test/config_test.rb +5 -3
- data/test/jid_test.rb +9 -0
- data/test/rake_test_loader.rb +1 -1
- data/test/router_test.rb +7 -7
- data/test/stanza/iq/disco_info_test.rb +2 -2
- data/test/stanza/iq/private_storage_test.rb +1 -1
- data/test/stanza/iq_test.rb +1 -1
- data/test/stanza/presence/subscribe_test.rb +1 -1
- data/test/stanza/pubsub/subscribe_test.rb +3 -3
- data/test/stanza/pubsub/unsubscribe_test.rb +3 -3
- data/test/storage_test.rb +15 -8
- data/test/store_test.rb +131 -0
- data/test/stream/client/ready_test.rb +6 -6
- data/test/stream/component/ready_test.rb +1 -1
- data/test/stream/http/ready_test.rb +46 -11
- data/test/stream/http/request_test.rb +83 -11
- data/test/stream/http/sessions_test.rb +2 -2
- data/web/chat/coffeescripts/chat.coffee +1 -1
- data/web/chat/index.html +9 -10
- data/web/chat/javascripts/app.js +1 -1
- data/web/lib/coffeescripts/button.coffee +1 -1
- data/web/lib/coffeescripts/contact.coffee +2 -2
- data/web/lib/coffeescripts/filter.coffee +1 -1
- data/web/lib/coffeescripts/layout.coffee +2 -2
- data/web/lib/coffeescripts/login.coffee +1 -1
- data/web/lib/coffeescripts/logout.coffee +2 -2
- data/web/lib/coffeescripts/navbar.coffee +1 -1
- data/web/lib/coffeescripts/notification.coffee +1 -1
- data/web/lib/coffeescripts/router.coffee +1 -1
- data/web/lib/coffeescripts/session.coffee +1 -1
- data/web/lib/coffeescripts/transfer.coffee +1 -1
- data/web/lib/javascripts/base.js +10 -9
- metadata +65 -43
- data/web/chat/javascripts/chat.js +0 -390
- data/web/chat/javascripts/init.js +0 -21
- data/web/lib/javascripts/button.js +0 -39
- data/web/lib/javascripts/contact.js +0 -94
- data/web/lib/javascripts/filter.js +0 -88
- data/web/lib/javascripts/layout.js +0 -48
- data/web/lib/javascripts/login.js +0 -88
- data/web/lib/javascripts/logout.js +0 -11
- data/web/lib/javascripts/navbar.js +0 -69
- data/web/lib/javascripts/notification.js +0 -26
- data/web/lib/javascripts/router.js +0 -105
- data/web/lib/javascripts/session.js +0 -291
- data/web/lib/javascripts/transfer.js +0 -124
data/lib/vines/command/ldap.rb
CHANGED
@@ -7,7 +7,7 @@ module Vines
|
|
7
7
|
raise 'vines ldap <domain>' unless opts[:args].size == 1
|
8
8
|
require opts[:config]
|
9
9
|
domain = opts[:args].first
|
10
|
-
unless storage = Config.instance.
|
10
|
+
unless storage = Config.instance.vhost(domain).storage rescue nil
|
11
11
|
raise "#{domain} virtual host not found in conf/config.rb"
|
12
12
|
end
|
13
13
|
unless storage.ldap?
|
data/lib/vines/command/schema.rb
CHANGED
@@ -7,7 +7,7 @@ module Vines
|
|
7
7
|
raise 'vines schema <domain>' unless opts[:args].size == 1
|
8
8
|
require opts[:config]
|
9
9
|
domain = opts[:args].first
|
10
|
-
unless storage = Config.instance.
|
10
|
+
unless storage = Config.instance.vhost(domain).storage rescue nil
|
11
11
|
raise "#{domain} virtual host not found in conf/config.rb"
|
12
12
|
end
|
13
13
|
unless storage.respond_to?(:create_schema)
|
data/lib/vines/config.rb
CHANGED
@@ -9,7 +9,7 @@ module Vines
|
|
9
9
|
class Config
|
10
10
|
LOG_LEVELS = %w[debug info warn error fatal].freeze
|
11
11
|
|
12
|
-
attr_reader :router
|
12
|
+
attr_reader :router
|
13
13
|
|
14
14
|
@@instance = nil
|
15
15
|
def self.configure(&block)
|
@@ -67,13 +67,19 @@ module Vines
|
|
67
67
|
|
68
68
|
# Return true if the domain is virtual hosted by this server.
|
69
69
|
def vhost?(domain)
|
70
|
-
|
70
|
+
!!vhost(domain)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return the Host config object for this domain if it's hosted by this
|
74
|
+
# server.
|
75
|
+
def vhost(domain)
|
76
|
+
@vhosts[domain.to_s]
|
71
77
|
end
|
72
78
|
|
73
79
|
# Returns the storage system for the domain or a Storage::Null instance if
|
74
80
|
# the domain is not hosted at this server.
|
75
81
|
def storage(domain)
|
76
|
-
host =
|
82
|
+
host = vhost(domain)
|
77
83
|
host ? host.storage : @null
|
78
84
|
end
|
79
85
|
|
@@ -112,7 +118,7 @@ module Vines
|
|
112
118
|
|
113
119
|
# Return true if private XML fragment storage is enabled for this domain.
|
114
120
|
def private_storage?(domain)
|
115
|
-
host =
|
121
|
+
host = vhost(domain)
|
116
122
|
host.private_storage? if host
|
117
123
|
end
|
118
124
|
|
@@ -191,7 +197,7 @@ module Vines
|
|
191
197
|
# Return true if all JIDs are allowed to exchange cross domain messages.
|
192
198
|
def cross_domain?(*jids)
|
193
199
|
!jids.flatten.index do |jid|
|
194
|
-
|
200
|
+
!vhost(jid.domain).cross_domain_messages?
|
195
201
|
end
|
196
202
|
end
|
197
203
|
end
|
data/lib/vines/jid.rb
CHANGED
@@ -4,13 +4,13 @@ module Vines
|
|
4
4
|
class JID
|
5
5
|
include Comparable
|
6
6
|
|
7
|
-
PATTERN =
|
7
|
+
PATTERN = /\A(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?\Z/.freeze
|
8
8
|
|
9
9
|
# http://tools.ietf.org/html/rfc6122#appendix-A
|
10
|
-
NODE_PREP = /[[:
|
10
|
+
NODE_PREP = /[[:cntrl:] "&'\/:<>@]/.freeze
|
11
11
|
|
12
12
|
# http://tools.ietf.org/html/rfc3454#appendix-C
|
13
|
-
NAME_PREP = /[[:
|
13
|
+
NAME_PREP = /[[:cntrl:] ]/.freeze
|
14
14
|
|
15
15
|
attr_reader :node, :domain, :resource
|
16
16
|
attr_writer :resource
|
@@ -15,7 +15,7 @@ module Vines
|
|
15
15
|
query.default_namespace = NS
|
16
16
|
unless to_pubsub_domain?
|
17
17
|
to = (validate_to || stream.domain).to_s
|
18
|
-
stream.config.
|
18
|
+
stream.config.vhost(to).disco_items.each do |domain|
|
19
19
|
query << el.document.create_element('item', 'jid' => domain)
|
20
20
|
end
|
21
21
|
end
|
data/lib/vines/storage/local.rb
CHANGED
data/lib/vines/store.rb
CHANGED
@@ -6,9 +6,12 @@ module Vines
|
|
6
6
|
# This uses the conf/certs/*.crt files as the list of trusted root
|
7
7
|
# CA certificates.
|
8
8
|
class Store
|
9
|
-
@@
|
9
|
+
@@sources = nil
|
10
10
|
|
11
|
-
|
11
|
+
# Create a certificate store to read certificate files from the given
|
12
|
+
# directory.
|
13
|
+
def initialize(dir)
|
14
|
+
@dir = File.expand_path(dir)
|
12
15
|
@store = OpenSSL::X509::Store.new
|
13
16
|
certs.each {|c| @store.add_cert(c) }
|
14
17
|
end
|
@@ -37,15 +40,53 @@ module Vines
|
|
37
40
|
# certificates are used to start the trust chain needed to validate certs
|
38
41
|
# we receive from clients and servers.
|
39
42
|
def certs
|
40
|
-
unless @@
|
43
|
+
unless @@sources
|
41
44
|
pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
pairs = Dir[File.join(@dir, '*.crt')].map do |name|
|
46
|
+
pems = File.read(name).scan(pattern)
|
47
|
+
certs = pems.map {|pem| OpenSSL::X509::Certificate.new(pem) }
|
48
|
+
certs.reject! {|cert| cert.not_after < Time.now }
|
49
|
+
[name, certs]
|
50
|
+
end
|
51
|
+
@@sources = Hash[pairs]
|
52
|
+
end
|
53
|
+
@@sources.values.flatten
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a pair of file names containing the public key certificate
|
57
|
+
# and matching private key for the given domain. This supports using
|
58
|
+
# wildcard certificate files to serve several subdomains.
|
59
|
+
#
|
60
|
+
# Finding the certificate and private key file for a domain follows these steps:
|
61
|
+
# - look for <domain>.crt and <domain>.key files in the conf/certs directory.
|
62
|
+
# if found, return those file names, else
|
63
|
+
# - inspect all conf/certs/*.crt files for certificates that contain the
|
64
|
+
# domain name either as the subject common name (CN) or as a DNS
|
65
|
+
# subjectAltName. The corresponding private key must be in a file of the
|
66
|
+
# same name as the certificate's, but with a .key extension.
|
67
|
+
#
|
68
|
+
# So in the simplest configuration, the tea.wonderland.lit encryption files would
|
69
|
+
# be named conf/certs/tea.wonderland.lit.crt and conf/certs/tea.wonderland.lit.key.
|
70
|
+
#
|
71
|
+
# However, in the case of a wildcard certificate for *.wonderland.lit, the
|
72
|
+
# files would be conf/certs/wonderland.lit.crt and conf/certs/wonderland.lit.key.
|
73
|
+
# These same two files would be returned for the subdomains of tea.wonderland.lit,
|
74
|
+
# crumpets.wonderland.lit, etc.
|
75
|
+
def files_for_domain(domain)
|
76
|
+
crt = File.expand_path("#{domain}.crt", @dir)
|
77
|
+
key = File.expand_path("#{domain}.key", @dir)
|
78
|
+
return [crt, key] if File.exists?(crt) && File.exists?(key)
|
79
|
+
|
80
|
+
# might be a wildcard cert file
|
81
|
+
@@sources.each do |file, certs|
|
82
|
+
certs.each do |cert|
|
83
|
+
if OpenSSL::SSL.verify_certificate_identity(cert, domain)
|
84
|
+
key = file.chomp(File.extname(file)) + '.key'
|
85
|
+
return [file, key] if File.exists?(file) && File.exists?(key)
|
86
|
+
end
|
87
|
+
end
|
47
88
|
end
|
48
|
-
|
89
|
+
nil
|
49
90
|
end
|
50
91
|
end
|
51
92
|
end
|
data/lib/vines/stream.rb
CHANGED
@@ -21,7 +21,7 @@ module Vines
|
|
21
21
|
@remote_addr, @local_addr = addresses
|
22
22
|
@user, @closed, @stanza_size = nil, false, 0
|
23
23
|
@bucket = TokenBucket.new(100, 10)
|
24
|
-
@store = Store.new
|
24
|
+
@store = Store.new(File.join(VINES_ROOT, 'conf', 'certs'))
|
25
25
|
@nodes = EM::Queue.new
|
26
26
|
process_node_queue
|
27
27
|
create_parser
|
@@ -71,7 +71,7 @@ module Vines
|
|
71
71
|
|
72
72
|
# Returns the Vines::Config::Host virtual host for the stream's domain.
|
73
73
|
def vhost
|
74
|
-
@config.
|
74
|
+
@config.vhost(domain)
|
75
75
|
end
|
76
76
|
|
77
77
|
# Reload the user's information into their active connections. Call this
|
@@ -118,14 +118,14 @@ module Vines
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def encrypt
|
121
|
-
cert, key =
|
122
|
-
start_tls(:
|
121
|
+
cert, key = @store.files_for_domain(domain)
|
122
|
+
start_tls(cert_chain_file: cert, private_key_file: key, verify_peer: true)
|
123
123
|
end
|
124
124
|
|
125
125
|
# Returns true if the TLS certificate and private key files for this domain
|
126
126
|
# exist and can be used to encrypt this stream.
|
127
127
|
def encrypt?
|
128
|
-
|
128
|
+
!@store.files_for_domain(domain).nil?
|
129
129
|
end
|
130
130
|
|
131
131
|
def unbind
|
@@ -235,10 +235,6 @@ module Vines
|
|
235
235
|
@state
|
236
236
|
end
|
237
237
|
|
238
|
-
def tls_files
|
239
|
-
%w[crt key].map {|ext| File.join(VINES_ROOT, 'conf', 'certs', "#{domain}.#{ext}") }
|
240
|
-
end
|
241
|
-
|
242
238
|
# Return true if this is a valid domain-only JID that can be used in
|
243
239
|
# stream initiation stanza headers.
|
244
240
|
def valid_address?(jid)
|
data/lib/vines/stream/http.rb
CHANGED
@@ -40,7 +40,9 @@ module Vines
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def process_request(request)
|
43
|
-
if request.path == self.bind
|
43
|
+
if request.path == self.bind && request.options?
|
44
|
+
request.reply_to_options
|
45
|
+
elsif request.path == self.bind
|
44
46
|
body = Nokogiri::XML(request.body).root
|
45
47
|
if session = Sessions[body['sid']]
|
46
48
|
@session = session
|
@@ -11,6 +11,7 @@ module Vines
|
|
11
11
|
NOT_MODIFIED = 'Not Modified'.freeze
|
12
12
|
IF_MODIFIED = 'If-Modified-Since'.freeze
|
13
13
|
TEXT_PLAIN = 'text/plain'.freeze
|
14
|
+
OPTIONS = 'OPTIONS'.freeze
|
14
15
|
CONTENT_TYPES = {
|
15
16
|
'html' => 'text/html; charset="utf-8"',
|
16
17
|
'js' => 'application/javascript; charset="utf-8"',
|
@@ -72,12 +73,33 @@ module Vines
|
|
72
73
|
body = node.to_s
|
73
74
|
header = [
|
74
75
|
"HTTP/1.1 200 OK",
|
76
|
+
"Access-Control-Allow-Origin: *",
|
75
77
|
"Content-Type: #{content_type}",
|
76
78
|
"Content-Length: #{body.bytesize}"
|
77
79
|
].join("\r\n")
|
78
80
|
@stream.stream_write([header, body].join("\r\n\r\n"))
|
79
81
|
end
|
80
82
|
|
83
|
+
# Return true if the request method is OPTIONS, signaling a
|
84
|
+
# CORS preflight check.
|
85
|
+
def options?
|
86
|
+
@method == OPTIONS
|
87
|
+
end
|
88
|
+
|
89
|
+
# Send a 200 OK response, allowing any origin domain to connect to the
|
90
|
+
# server, in response to CORS preflight OPTIONS requests. This allows
|
91
|
+
# any web application using strophe.js to connect to our BOSH port.
|
92
|
+
def reply_to_options
|
93
|
+
allow = @headers['Access-Control-Request-Headers']
|
94
|
+
headers = [
|
95
|
+
"Access-Control-Allow-Origin: *",
|
96
|
+
"Access-Control-Allow-Methods: POST, GET, OPTIONS",
|
97
|
+
"Access-Control-Allow-Headers: #{allow}",
|
98
|
+
"Access-Control-Max-Age: #{60 * 60 * 24 * 30}"
|
99
|
+
]
|
100
|
+
send_status(200, 'OK', headers)
|
101
|
+
end
|
102
|
+
|
81
103
|
private
|
82
104
|
|
83
105
|
# Attempt to rebuild the full request URI from the Host header. If it
|
data/lib/vines/version.rb
CHANGED
data/test/config/host_test.rb
CHANGED
@@ -46,7 +46,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
|
-
refute_nil config.
|
49
|
+
refute_nil config.vhost('wonderland.lit').storage
|
50
50
|
end
|
51
51
|
|
52
52
|
def test_ldap_added_to_storage
|
@@ -81,8 +81,8 @@ class HostTest < MiniTest::Unit::TestCase
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
%w[wonderland.lit verona.lit].each do |domain|
|
84
|
-
refute_nil config.
|
85
|
-
assert config.
|
84
|
+
refute_nil config.vhost(domain).storage.ldap
|
85
|
+
assert config.vhost(domain).storage.ldap?
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
@@ -225,7 +225,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
225
225
|
components 'TEA' => 'secr3t', CAKE: 'Passw0rd'
|
226
226
|
end
|
227
227
|
end
|
228
|
-
host = config.
|
228
|
+
host = config.vhost('wonderland.lit')
|
229
229
|
refute_nil host
|
230
230
|
assert_equal 2, host.components.size
|
231
231
|
assert_equal host.components['tea.wonderland.lit'], 'secr3t'
|
@@ -239,7 +239,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
239
239
|
components 'tea' => 'secr3t', cake: 'passw0rd'
|
240
240
|
end
|
241
241
|
end
|
242
|
-
host = config.
|
242
|
+
host = config.vhost('wonderland.lit')
|
243
243
|
refute_nil host
|
244
244
|
refute host.component?(nil)
|
245
245
|
refute host.component?('tea')
|
@@ -309,7 +309,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
309
309
|
pubsub 'TEA', :CAKE
|
310
310
|
end
|
311
311
|
end
|
312
|
-
host = config.
|
312
|
+
host = config.vhost('wonderland.lit')
|
313
313
|
refute_nil host
|
314
314
|
assert_equal 2, host.pubsubs.size
|
315
315
|
refute_nil host.pubsubs['tea.wonderland.lit']
|
@@ -323,7 +323,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
323
323
|
pubsub 'tea', :cake
|
324
324
|
end
|
325
325
|
end
|
326
|
-
host = config.
|
326
|
+
host = config.vhost('wonderland.lit')
|
327
327
|
refute_nil host
|
328
328
|
refute host.pubsub?(nil)
|
329
329
|
refute host.pubsub?('tea')
|
@@ -348,7 +348,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
348
348
|
storage(:fs) { dir Dir.tmpdir }
|
349
349
|
end
|
350
350
|
end
|
351
|
-
host = config.
|
351
|
+
host = config.vhost('wonderland.lit')
|
352
352
|
refute_nil host
|
353
353
|
refute host.private_storage?
|
354
354
|
end
|
@@ -360,7 +360,7 @@ class HostTest < MiniTest::Unit::TestCase
|
|
360
360
|
storage(:fs) { dir Dir.tmpdir }
|
361
361
|
end
|
362
362
|
end
|
363
|
-
host = config.
|
363
|
+
host = config.vhost('wonderland.lit')
|
364
364
|
refute_nil host
|
365
365
|
assert host.private_storage?
|
366
366
|
assert config.private_storage?('wonderland.lit')
|
data/test/config/pubsub_test.rb
CHANGED
@@ -52,7 +52,7 @@ class ConfigPubSubTest < MiniTest::Unit::TestCase
|
|
52
52
|
def test_subscribe_remote_jid_is_allowed
|
53
53
|
topic = 'remote_jids_allowed'
|
54
54
|
jid = 'romeo@verona.lit'
|
55
|
-
@config.
|
55
|
+
@config.vhost('wonderland.lit').cross_domain_messages true
|
56
56
|
@pubsub.add_node(topic)
|
57
57
|
@pubsub.subscribe(topic, jid)
|
58
58
|
assert @pubsub.subscribed?(topic, jid)
|
@@ -105,7 +105,7 @@ class ConfigPubSubTest < MiniTest::Unit::TestCase
|
|
105
105
|
alice = Vines::JID.new('alice@wonderland.lit')
|
106
106
|
romeo = Vines::JID.new('romeo@verona.lit')
|
107
107
|
|
108
|
-
@config.
|
108
|
+
@config.vhost('wonderland.lit').cross_domain_messages true
|
109
109
|
def @config.router
|
110
110
|
unless @mock_router
|
111
111
|
@mock_router = MiniTest::Mock.new
|
data/test/config_test.rb
CHANGED
@@ -60,8 +60,10 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
60
60
|
storage(:fs) { dir Dir.tmpdir }
|
61
61
|
end
|
62
62
|
end
|
63
|
-
|
63
|
+
refute_nil config.vhost('wonderland.lit')
|
64
|
+
refute_nil config.vhost(Vines::JID.new('wonderland.lit'))
|
64
65
|
assert config.vhost?('wonderland.lit')
|
66
|
+
assert config.vhost?(Vines::JID.new('wonderland.lit'))
|
65
67
|
refute config.vhost?('alice@wonderland.lit')
|
66
68
|
refute config.vhost?('tea.wonderland.lit')
|
67
69
|
refute config.vhost?('bogus')
|
@@ -394,8 +396,8 @@ class ConfigTest < MiniTest::Unit::TestCase
|
|
394
396
|
storage(:fs) { dir Dir.tmpdir }
|
395
397
|
end
|
396
398
|
end
|
397
|
-
refute config.
|
398
|
-
assert config.
|
399
|
+
refute config.vhost('wonderland.lit').cross_domain_messages?
|
400
|
+
assert config.vhost('verona.lit').cross_domain_messages?
|
399
401
|
end
|
400
402
|
|
401
403
|
def test_local_jid?
|
data/test/jid_test.rb
CHANGED
@@ -115,10 +115,19 @@ class JidTest < MiniTest::Unit::TestCase
|
|
115
115
|
assert_raises(ArgumentError) { Vines::JID.new(%q{alice>s@wonderland.lit}) }
|
116
116
|
assert_raises(ArgumentError) { Vines::JID.new("alice\u0000s@wonderland.lit") }
|
117
117
|
assert_raises(ArgumentError) { Vines::JID.new("alice\ts@wonderland.lit") }
|
118
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice\rs@wonderland.lit") }
|
119
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice\ns@wonderland.lit") }
|
120
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice\vs@wonderland.lit") }
|
121
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice\fs@wonderland.lit") }
|
118
122
|
assert_raises(ArgumentError) { Vines::JID.new(" alice@wonderland.lit") }
|
119
123
|
assert_raises(ArgumentError) { Vines::JID.new("alice@wonderland.lit ") }
|
120
124
|
assert_raises(ArgumentError) { Vines::JID.new("alice s@wonderland.lit") }
|
121
125
|
assert_raises(ArgumentError) { Vines::JID.new("alice@w onderland.lit") }
|
126
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice@w\tonderland.lit") }
|
127
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice@w\ronderland.lit") }
|
128
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice@w\nonderland.lit") }
|
129
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice@w\vonderland.lit") }
|
130
|
+
assert_raises(ArgumentError) { Vines::JID.new("alice@w\fonderland.lit") }
|
122
131
|
assert_raises(ArgumentError) { Vines::JID.new("alice@wonderland.lit/ res") }
|
123
132
|
assert_raises(ArgumentError) { Vines::JID.new("alice@w\u0000onderland.lit") }
|
124
133
|
assert_raises(ArgumentError) { Vines::JID.new("alice@wonderland.lit/\u0000res") }
|