tp-blather 0.8.2

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 (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,84 @@
1
+ module Blather
2
+
3
+ # Stream Errors
4
+ # [RFC3920 Section 9.3](http://xmpp.org/rfcs/rfc3920.html#streams-error-rules)
5
+ #
6
+ # @handler :stream_error
7
+ class StreamError < BlatherError
8
+ # @private
9
+ STREAM_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-streams'
10
+
11
+ register :stream_error
12
+
13
+ attr_reader :text, :extras
14
+
15
+ # Factory method for instantiating the proper class for the error
16
+ #
17
+ # @param [Blather::XMPPNode] node the importable node
18
+ def self.import(node)
19
+ name = node.find_first('descendant::*[name()!="text"]', STREAM_ERR_NS).element_name
20
+
21
+ text = node.find_first 'descendant::*[name()="text"]', STREAM_ERR_NS
22
+ text = text.content if text
23
+
24
+ extras = node.find("descendant::*[namespace-uri()!='#{STREAM_ERR_NS}']").map { |n| n }
25
+
26
+ self.new name, text, extras
27
+ end
28
+
29
+ # Create a new Stream Error
30
+ # [RFC3920 Section 4.7.2](http://xmpp.org/rfcs/rfc3920.html#rfc.section.4.7.2)
31
+ #
32
+ # @param [String] name the error name
33
+ # @param [String, nil] text optional error text
34
+ # @param [Array<Blather::XMPPNode>] extras an array of extras to attach to the
35
+ # error
36
+ def initialize(name, text = nil, extras = [])
37
+ @name = name
38
+ @text = text
39
+ @extras = extras
40
+ end
41
+
42
+ # The error name
43
+ #
44
+ # @return [Symbol]
45
+ def name
46
+ @name.gsub('-','_').to_sym
47
+ end
48
+
49
+ # Creates an XML node from the error
50
+ #
51
+ # @return [Blather::XMPPNode]
52
+ def to_node
53
+ node = XMPPNode.new('error')
54
+ node.namespace = {'stream' => Blather::Stream::STREAM_NS}
55
+
56
+ node << (err = XMPPNode.new(@name, node.document))
57
+ err.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
58
+
59
+ if self.text
60
+ node << (text = XMPPNode.new('text', node.document))
61
+ text.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
62
+ text.content = self.text
63
+ end
64
+
65
+ self.extras.each { |extra| node << extra.dup }
66
+ node
67
+ end
68
+
69
+ # Convert the object to a proper node then convert it to a string
70
+ #
71
+ # @return [String]
72
+ def to_xml
73
+ to_node.to_s
74
+ end
75
+
76
+ # @private
77
+ def inspect
78
+ "Stream Error (#{@name}): #{self.text}" + (self.extras.empty? ? '' : " [#{self.extras}]")
79
+ end
80
+ # @private
81
+ alias_method :to_s, :inspect
82
+ end # StreamError
83
+
84
+ end # Blather
@@ -0,0 +1,107 @@
1
+ module Blather
2
+ # File Transfer helper
3
+ # Takes care of accepting, declining and offering file transfers through the stream
4
+ class FileTransfer
5
+
6
+ # Set this to false if you don't want to use In-Band Bytestreams
7
+ attr_accessor :allow_ibb
8
+
9
+ # Set this to false if you don't want to use SOCKS5 Bytestreams
10
+ attr_accessor :allow_s5b
11
+
12
+ # Set this to true if you want SOCKS5 Bytestreams to attempt to use private network addresses
13
+ attr_accessor :allow_private_ips
14
+
15
+ # Create a new FileTransfer
16
+ #
17
+ # @param [Blather::Stream] stream the stream the file transfer should use
18
+ # @param [Blather::Stanza::Iq::Si] iq a si iq used to stream-initiation
19
+ def initialize(stream, iq = nil)
20
+ @stream = stream
21
+ @allow_s5b = true
22
+ @allow_ibb = true
23
+
24
+ @iq = iq
25
+ end
26
+
27
+ # Accept an incoming file-transfer
28
+ #
29
+ # @param [module] handler the handler for incoming data, see Blather::FileTransfer::SimpleFileReceiver for an example
30
+ # @param [Array] params the params to be passed into the handler
31
+ def accept(handler, *params)
32
+ answer = @iq.reply
33
+
34
+ answer.si.feature.x.type = :submit
35
+
36
+ supported_methods = @iq.si.feature.x.field("stream-method").options.map(&:value)
37
+ if supported_methods.include?(Stanza::Iq::S5b::NS_S5B) and @allow_s5b
38
+ answer.si.feature.x.fields = {:var => 'stream-method', :value => Stanza::Iq::S5b::NS_S5B}
39
+
40
+ @stream.register_handler :s5b_open, :from => @iq.from do |iq|
41
+ transfer = Blather::FileTransfer::S5b.new(@stream, iq)
42
+ transfer.allow_ibb_fallback = true if @allow_ibb
43
+ transfer.allow_private_ips = true if @allow_private_ips
44
+ transfer.accept(handler, *params)
45
+ true
46
+ end
47
+
48
+ @stream.write answer
49
+ elsif supported_methods.include?(Stanza::Iq::Ibb::NS_IBB) and @allow_ibb
50
+ answer.si.feature.x.fields = {:var => 'stream-method', :value => Stanza::Iq::Ibb::NS_IBB}
51
+
52
+ @stream.register_handler :ibb_open, :from => @iq.from do |iq|
53
+ transfer = Blather::FileTransfer::Ibb.new(@stream, iq)
54
+ transfer.accept(handler, *params)
55
+ true
56
+ end
57
+
58
+ @stream.write answer
59
+ else
60
+ reason = XMPPNode.new('no-valid-streams')
61
+ reason.namespace = Blather::Stanza::Iq::Si::NS_SI
62
+
63
+ @stream.write StanzaError.new(@iq, 'bad-request', 'cancel', nil, [reason]).to_node
64
+ end
65
+ end
66
+
67
+ # Decline an incoming file-transfer
68
+ def decline
69
+ answer = StanzaError.new(@iq, 'forbidden', 'cancel', 'Offer declined').to_node
70
+
71
+ @stream.write answer
72
+ end
73
+
74
+ # Offer a file to somebody, not implemented yet
75
+ def offer
76
+ # TODO: implement
77
+ end
78
+
79
+ # Simple handler for incoming file transfers
80
+ #
81
+ # You can define your own handler and pass it to the accept method.
82
+ module SimpleFileReceiver
83
+ def initialize(path, size)
84
+ @path = path
85
+ @size = size
86
+ @transferred = 0
87
+ end
88
+
89
+ # @private
90
+ def post_init
91
+ @file = File.open(@path, "w")
92
+ end
93
+
94
+ # @private
95
+ def receive_data(data)
96
+ @transferred += data.size
97
+ @file.write data
98
+ end
99
+
100
+ # @private
101
+ def unbind
102
+ @file.close
103
+ File.delete(@path) unless @transferred == @size
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,68 @@
1
+ require "base64"
2
+
3
+ module Blather
4
+ class FileTransfer
5
+ # In-Band Bytestreams Transfer helper
6
+ # Takes care of accepting, declining and offering file transfers through the stream
7
+ class Ibb
8
+ def initialize(stream, iq)
9
+ @stream = stream
10
+ @iq = iq
11
+ @seq = 0
12
+ end
13
+
14
+ # Accept an incoming file-transfer
15
+ #
16
+ # @param [module] handler the handler for incoming data, see Blather::FileTransfer::SimpleFileReceiver for an example
17
+ # @param [Array] params the params to be passed into the handler
18
+ def accept(handler, *params)
19
+ @io_read, @io_write = IO.pipe
20
+ EM::attach @io_read, handler, *params
21
+
22
+ @stream.register_handler :ibb_data, :from => @iq.from, :sid => @iq.sid do |iq|
23
+ if iq.data['seq'] == @seq.to_s
24
+ begin
25
+ @io_write << Base64.decode64(iq.data.content)
26
+
27
+ @stream.write iq.reply
28
+
29
+ @seq += 1
30
+ @seq = 0 if @seq > 65535
31
+ rescue Errno::EPIPE => e
32
+ @stream.write StanzaError.new(iq, 'not-acceptable', :cancel).to_node
33
+ end
34
+ else
35
+ @stream.write StanzaError.new(iq, 'unexpected-request', :wait).to_node
36
+ end
37
+ true
38
+ end
39
+
40
+ @stream.register_handler :ibb_close, :from => @iq.from, :sid => @iq.sid do |iq|
41
+ @stream.write iq.reply
42
+ @stream.clear_handlers :ibb_data, :from => @iq.from, :sid => @iq.sid
43
+ @stream.clear_handlers :ibb_close, :from => @iq.from, :sid => @iq.sid
44
+
45
+ @io_write.close
46
+ true
47
+ end
48
+
49
+ @stream.clear_handlers :ibb_open, :from => @iq.from
50
+ @stream.clear_handlers :ibb_open, :from => @iq.from, :sid => @iq.sid
51
+ @stream.write @iq.reply
52
+ end
53
+
54
+ # Decline an incoming file-transfer
55
+ def decline
56
+ @stream.clear_handlers :ibb_open, :from => @iq.from
57
+ @stream.clear_handlers :ibb_data, :from => @iq.from, :sid => @iq.sid
58
+ @stream.clear_handlers :ibb_close, :from => @iq.from, :sid => @iq.sid
59
+ @stream.write StanzaError.new(@iq, 'not-acceptable', :cancel).to_node
60
+ end
61
+
62
+ # Offer a file to somebody, not implemented yet
63
+ def offer
64
+ # TODO: implement
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,114 @@
1
+ module Blather
2
+ class FileTransfer
3
+ # SOCKS5 Bytestreams Transfer helper
4
+ # Takes care of accepting, declining and offering file transfers through the stream
5
+ class S5b
6
+
7
+ # Set this to false if you don't want to fallback to In-Band Bytestreams
8
+ attr_accessor :allow_ibb_fallback
9
+
10
+ # Set this to true if the buddies of your bot will be in the same local network
11
+ #
12
+ # Usually IM clients advertise all network addresses which they can determine.
13
+ # Skipping the local ones can save time if your bot is not in the same local network as it's buddies
14
+ attr_accessor :allow_private_ips
15
+
16
+ def initialize(stream, iq)
17
+ @stream = stream
18
+ @iq = iq
19
+ @allow_ibb_fallback = true
20
+ @allow_private_ips = false
21
+ end
22
+
23
+ # Accept an incoming file-transfer
24
+ #
25
+ # @param [module] handler the handler for incoming data, see Blather::FileTransfer::SimpleFileReceiver for an example
26
+ # @param [Array] params the params to be passed into the handler
27
+ def accept(handler, *params)
28
+ @streamhosts = @iq.streamhosts
29
+ @streamhosts.delete_if {|s| begin IPAddr.new(s.host).private? rescue false end } unless @allow_private_ips
30
+ @socket_address = Digest::SHA1.hexdigest("#{@iq.sid}#{@iq.from}#{@iq.to}")
31
+
32
+ @handler = handler
33
+ @params = params
34
+
35
+ connect_next_streamhost
36
+ @stream.clear_handlers :s5b_open, :from => @iq.from
37
+ end
38
+
39
+ # Decline an incoming file-transfer
40
+ def decline
41
+ @stream.clear_handlers :s5b_open, :from => @iq.from
42
+ @stream.write StanzaError.new(@iq, 'not-acceptable', :auth).to_node
43
+ end
44
+
45
+ # Offer a file to somebody, not implemented yet
46
+ def offer
47
+ # TODO: implement
48
+ end
49
+
50
+ private
51
+
52
+ def connect_next_streamhost
53
+ if streamhost = @streamhosts.shift
54
+ connect(streamhost)
55
+ else
56
+ if @allow_ibb_fallback
57
+ @stream.register_handler :ibb_open, :from => @iq.from, :sid => @iq.sid do |iq|
58
+ transfer = Blather::FileTransfer::Ibb.new(@stream, iq)
59
+ transfer.accept(@handler, *@params)
60
+ true
61
+ end
62
+ end
63
+
64
+ @stream.write StanzaError.new(@iq, 'item-not-found', :cancel).to_node
65
+ end
66
+ end
67
+
68
+ def connect(streamhost)
69
+ begin
70
+ socket = EM.connect streamhost.host, streamhost.port, SocketConnection, @socket_address, 0, @handler, *@params
71
+
72
+ socket.callback do
73
+ answer = @iq.reply
74
+ answer.streamhosts = nil
75
+ answer.streamhost_used = streamhost.jid
76
+
77
+ @stream.write answer
78
+ end
79
+
80
+ socket.errback do
81
+ connect_next_streamhost
82
+ end
83
+ rescue EventMachine::ConnectionError => e
84
+ connect_next_streamhost
85
+ end
86
+ end
87
+
88
+ # @private
89
+ class SocketConnection < EM::P::Socks5
90
+ include EM::Deferrable
91
+
92
+ def initialize(host, port, handler, *params)
93
+ super(host, port)
94
+ @@handler = handler
95
+ @params = params
96
+ end
97
+
98
+ def post_init
99
+ self.succeed
100
+
101
+ class << self
102
+ include @@handler
103
+ end
104
+ send(:initialize, *@params)
105
+ post_init
106
+ end
107
+
108
+ def unbind
109
+ self.fail if @socks_state != :connected
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,141 @@
1
+ module Blather
2
+
3
+ # Jabber ID or JID
4
+ #
5
+ # See [RFC 3920 Section 3 - Addressing](http://xmpp.org/rfcs/rfc3920.html#addressing)
6
+ #
7
+ # An entity is anything that can be considered a network endpoint (i.e., an
8
+ # ID on the network) and that can communicate using XMPP. All such entities
9
+ # are uniquely addressable in a form that is consistent with RFC 2396 [URI].
10
+ # For historical reasons, the address of an XMPP entity is called a Jabber
11
+ # Identifier or JID. A valid JID contains a set of ordered elements formed
12
+ # of a domain identifier, node identifier, and resource identifier.
13
+ #
14
+ # The syntax for a JID is defined below using the Augmented Backus-Naur Form
15
+ # as defined in [ABNF]. (The IPv4address and IPv6address rules are defined
16
+ # in Appendix B of [IPv6]; the allowable character sequences that conform to
17
+ # the node rule are defined by the Nodeprep profile of [STRINGPREP] as
18
+ # documented in Appendix A of this memo; the allowable character sequences
19
+ # that conform to the resource rule are defined by the Resourceprep profile
20
+ # of [STRINGPREP] as documented in Appendix B of this memo; and the
21
+ # sub-domain rule makes reference to the concept of an internationalized
22
+ # domain label as described in [IDNA].)
23
+ #
24
+ # jid = [ node "@" ] domain [ "/" resource ]
25
+ # domain = fqdn / address-literal
26
+ # fqdn = (sub-domain 1*("." sub-domain))
27
+ # sub-domain = (internationalized domain label)
28
+ # address-literal = IPv4address / IPv6address
29
+ #
30
+ # All JIDs are based on the foregoing structure. The most common use of this
31
+ # structure is to identify an instant messaging user, the server to which
32
+ # the user connects, and the user's connected resource (e.g., a specific
33
+ # client) in the form of <user@host/resource>. However, node types other
34
+ # than clients are possible; for example, a specific chat room offered by a
35
+ # multi-user chat service could be addressed as <room@service> (where "room"
36
+ # is the name of the chat room and "service" is the hostname of the
37
+ # multi-user chat service) and a specific occupant of such a room could be
38
+ # addressed as <room@service/nick> (where "nick" is the occupant's room
39
+ # nickname). Many other JID types are possible (e.g., <domain/resource>
40
+ # could be a server-side script or service).
41
+ #
42
+ # Each allowable portion of a JID (node identifier, domain identifier, and
43
+ # resource identifier) MUST NOT be more than 1023 bytes in length, resulting
44
+ # in a maximum total size (including the '@' and '/' separators) of 3071
45
+ # bytes.
46
+ class JID
47
+ include Comparable
48
+
49
+ # Validating pattern for JID string
50
+ PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.freeze
51
+
52
+ attr_reader :node,
53
+ :domain,
54
+ :resource
55
+
56
+ # @private
57
+ def self.new(node, domain = nil, resource = nil)
58
+ node.is_a?(JID) ? node : super
59
+ end
60
+
61
+ # Create a new JID object
62
+ #
63
+ # @overload initialize(jid)
64
+ # Passes the jid object right back out
65
+ # @param [Blather::JID] jid a jid object
66
+ # @overload initialize(jid)
67
+ # Creates a new JID parsed out of the provided jid
68
+ # @param [String] jid a jid in the standard format
69
+ # ("node@domain/resource")
70
+ # @overload initialize(node, domain = nil, resource = nil)
71
+ # Creates a new JID
72
+ # @param [String] node the node of the JID
73
+ # @param [String, nil] domian the domain of the JID
74
+ # @param [String, nil] resource the resource of the JID
75
+ # @raise [ArgumentError] if the parts of the JID are too large (1023 bytes)
76
+ # @return [Blather::JID] a new jid object
77
+ def initialize(node, domain = nil, resource = nil)
78
+ @resource = resource
79
+ @domain = domain
80
+ @node = node
81
+
82
+ if @domain.nil? && @resource.nil?
83
+ @node, @domain, @resource = @node.to_s.scan(PATTERN).first
84
+ end
85
+
86
+ raise ArgumentError, 'Node too long' if (@node || '').length > 1023
87
+ raise ArgumentError, 'Domain too long' if (@domain || '').length > 1023
88
+ raise ArgumentError, 'Resource too long' if (@resource || '').length > 1023
89
+ end
90
+
91
+ # Turn the JID into a string
92
+ #
93
+ # * ""
94
+ # * "domain"
95
+ # * "node@domain"
96
+ # * "domain/resource"
97
+ # * "node@domain/resource"
98
+ #
99
+ # @return [String] the JID as a string
100
+ def to_s
101
+ s = @domain
102
+ s = "#{@node}@#{s}" if @node
103
+ s = "#{s}/#{@resource}" if @resource
104
+ s
105
+ end
106
+
107
+ # Returns a new JID with resource removed.
108
+ #
109
+ # @return [Blather::JID] a new JID without a resource
110
+ def stripped
111
+ dup.strip!
112
+ end
113
+
114
+ # Removes the resource (sets it to nil)
115
+ #
116
+ # @return [Blather::JID] the JID without a resource
117
+ def strip!
118
+ @resource = nil
119
+ self
120
+ end
121
+
122
+ # Compare two JIDs, helpful for sorting etc.
123
+ #
124
+ # String representations are compared, see JID#to_s
125
+ #
126
+ # @param [#to_s] other a JID to comare against
127
+ # @return [Fixnum<-1, 0, 1>]
128
+ def <=>(other)
129
+ to_s.downcase <=> other.to_s.downcase
130
+ end
131
+ alias_method :eql?, :==
132
+
133
+ # Test if JID is stripped
134
+ #
135
+ # @return [true, false]
136
+ def stripped?
137
+ @resource.nil?
138
+ end
139
+ end # JID
140
+
141
+ end # Blather