vines 0.4.0 → 0.4.1

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.
Files changed (66) hide show
  1. data/LICENSE +1 -1
  2. data/Rakefile +33 -53
  3. data/conf/certs/README +10 -3
  4. data/conf/certs/ca-bundle.crt +55 -410
  5. data/lib/vines/cluster/connection.rb +1 -1
  6. data/lib/vines/command/ldap.rb +1 -1
  7. data/lib/vines/command/schema.rb +1 -1
  8. data/lib/vines/config.rb +11 -5
  9. data/lib/vines/jid.rb +3 -3
  10. data/lib/vines/stanza/iq/disco_items.rb +1 -1
  11. data/lib/vines/storage/local.rb +1 -0
  12. data/lib/vines/store.rb +50 -9
  13. data/lib/vines/stream.rb +5 -9
  14. data/lib/vines/stream/client/session.rb +1 -1
  15. data/lib/vines/stream/http.rb +3 -1
  16. data/lib/vines/stream/http/ready.rb +5 -1
  17. data/lib/vines/stream/http/request.rb +22 -0
  18. data/lib/vines/version.rb +1 -1
  19. data/test/config/host_test.rb +9 -9
  20. data/test/config/pubsub_test.rb +2 -2
  21. data/test/config_test.rb +5 -3
  22. data/test/jid_test.rb +9 -0
  23. data/test/rake_test_loader.rb +1 -1
  24. data/test/router_test.rb +7 -7
  25. data/test/stanza/iq/disco_info_test.rb +2 -2
  26. data/test/stanza/iq/private_storage_test.rb +1 -1
  27. data/test/stanza/iq_test.rb +1 -1
  28. data/test/stanza/presence/subscribe_test.rb +1 -1
  29. data/test/stanza/pubsub/subscribe_test.rb +3 -3
  30. data/test/stanza/pubsub/unsubscribe_test.rb +3 -3
  31. data/test/storage_test.rb +15 -8
  32. data/test/store_test.rb +131 -0
  33. data/test/stream/client/ready_test.rb +6 -6
  34. data/test/stream/component/ready_test.rb +1 -1
  35. data/test/stream/http/ready_test.rb +46 -11
  36. data/test/stream/http/request_test.rb +83 -11
  37. data/test/stream/http/sessions_test.rb +2 -2
  38. data/web/chat/coffeescripts/chat.coffee +1 -1
  39. data/web/chat/index.html +9 -10
  40. data/web/chat/javascripts/app.js +1 -1
  41. data/web/lib/coffeescripts/button.coffee +1 -1
  42. data/web/lib/coffeescripts/contact.coffee +2 -2
  43. data/web/lib/coffeescripts/filter.coffee +1 -1
  44. data/web/lib/coffeescripts/layout.coffee +2 -2
  45. data/web/lib/coffeescripts/login.coffee +1 -1
  46. data/web/lib/coffeescripts/logout.coffee +2 -2
  47. data/web/lib/coffeescripts/navbar.coffee +1 -1
  48. data/web/lib/coffeescripts/notification.coffee +1 -1
  49. data/web/lib/coffeescripts/router.coffee +1 -1
  50. data/web/lib/coffeescripts/session.coffee +1 -1
  51. data/web/lib/coffeescripts/transfer.coffee +1 -1
  52. data/web/lib/javascripts/base.js +10 -9
  53. metadata +65 -43
  54. data/web/chat/javascripts/chat.js +0 -390
  55. data/web/chat/javascripts/init.js +0 -21
  56. data/web/lib/javascripts/button.js +0 -39
  57. data/web/lib/javascripts/contact.js +0 -94
  58. data/web/lib/javascripts/filter.js +0 -88
  59. data/web/lib/javascripts/layout.js +0 -48
  60. data/web/lib/javascripts/login.js +0 -88
  61. data/web/lib/javascripts/logout.js +0 -11
  62. data/web/lib/javascripts/navbar.js +0 -69
  63. data/web/lib/javascripts/notification.js +0 -26
  64. data/web/lib/javascripts/router.js +0 -105
  65. data/web/lib/javascripts/session.js +0 -291
  66. data/web/lib/javascripts/transfer.js +0 -124
@@ -7,7 +7,7 @@ module Vines
7
7
  attr_accessor :host, :port, :database, :password
8
8
 
9
9
  def initialize
10
- @redis = nil
10
+ @redis, @host, @port, @database, @password = nil, nil, nil, nil, nil
11
11
  end
12
12
 
13
13
  # Return a shared redis connection.
@@ -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.vhosts[domain].storage rescue nil
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?
@@ -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.vhosts[domain].storage rescue nil
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)
@@ -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, :vhosts
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
- @vhosts.key?(domain.to_s)
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 = @vhosts[domain.to_s]
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 = @vhosts[domain.to_s]
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
- !@vhosts[jid.domain].cross_domain_messages?
200
+ !vhost(jid.domain).cross_domain_messages?
195
201
  end
196
202
  end
197
203
  end
@@ -4,13 +4,13 @@ module Vines
4
4
  class JID
5
5
  include Comparable
6
6
 
7
- PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.freeze
7
+ PATTERN = /\A(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?\Z/.freeze
8
8
 
9
9
  # http://tools.ietf.org/html/rfc6122#appendix-A
10
- NODE_PREP = /[[:space:][:cntrl:]"&'\/:<>@]/.freeze
10
+ NODE_PREP = /[[:cntrl:] "&'\/:<>@]/.freeze
11
11
 
12
12
  # http://tools.ietf.org/html/rfc3454#appendix-C
13
- NAME_PREP = /[[:space:][:cntrl:]]/.freeze
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.vhosts[to].disco_items.each do |domain|
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
@@ -9,6 +9,7 @@ module Vines
9
9
  register :fs
10
10
 
11
11
  def initialize(&block)
12
+ @dir = nil
12
13
  instance_eval(&block)
13
14
  unless @dir && File.directory?(@dir) && File.writable?(@dir)
14
15
  raise 'Must provide a writable storage directory'
@@ -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
- @@certs = nil
9
+ @@sources = nil
10
10
 
11
- def initialize
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 @@certs
43
+ unless @@sources
41
44
  pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
42
- dir = File.join(VINES_ROOT, 'conf', 'certs')
43
- certs = Dir[File.join(dir, '*.crt')].map {|f| File.read(f) }
44
- certs = certs.map {|c| c.scan(pattern) }.flatten
45
- certs.map! {|c| OpenSSL::X509::Certificate.new(c) }
46
- @@certs = certs.reject {|c| c.not_after < Time.now }
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
- @@certs
89
+ nil
49
90
  end
50
91
  end
51
92
  end
@@ -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.vhosts[domain]
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 = tls_files
122
- start_tls(:private_key_file => key, :cert_chain_file => cert, :verify_peer => true)
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
- tls_files.all? {|f| File.exists?(f) }
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)
@@ -196,7 +196,7 @@ module Vines
196
196
 
197
197
  def unsubscribe_pubsub
198
198
  if connected?
199
- @config.vhosts[@user.jid.domain].unsubscribe_pubsub(@user.jid)
199
+ @config.vhost(@user.jid.domain).unsubscribe_pubsub(@user.jid)
200
200
  end
201
201
  end
202
202
 
@@ -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,7 +11,11 @@ module Vines
11
11
  raise StreamErrors::NotAuthorized
12
12
  end
13
13
  stream.parse_body(node).each do |child|
14
- super(child)
14
+ begin
15
+ super(child)
16
+ rescue StanzaError => e
17
+ stream.error(e)
18
+ end
15
19
  end
16
20
  stream.terminate if terminate?(node)
17
21
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Vines
4
- VERSION = '0.4.0'
4
+ VERSION = '0.4.1'
5
5
  end
@@ -46,7 +46,7 @@ class HostTest < MiniTest::Unit::TestCase
46
46
  end
47
47
  end
48
48
  end
49
- refute_nil config.vhosts['wonderland.lit'].storage
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.vhosts[domain].storage.ldap
85
- assert config.vhosts[domain].storage.ldap?
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.vhosts['wonderland.lit']
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.vhosts['wonderland.lit']
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.vhosts['wonderland.lit']
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.vhosts['wonderland.lit']
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.vhosts['wonderland.lit']
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.vhosts['wonderland.lit']
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')
@@ -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.vhosts['wonderland.lit'].cross_domain_messages true
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.vhosts['wonderland.lit'].cross_domain_messages true
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
@@ -60,8 +60,10 @@ class ConfigTest < MiniTest::Unit::TestCase
60
60
  storage(:fs) { dir Dir.tmpdir }
61
61
  end
62
62
  end
63
- assert_equal ['wonderland.lit'], config.vhosts.keys
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.vhosts['wonderland.lit'].cross_domain_messages?
398
- assert config.vhosts['verona.lit'].cross_domain_messages?
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?
@@ -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") }