blather 0.4.14 → 0.4.15

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 (73) hide show
  1. data/lib/blather.rb +8 -0
  2. data/lib/blather/client/client.rb +9 -1
  3. data/lib/blather/client/dsl/pubsub.rb +2 -2
  4. data/lib/blather/core_ext/active_support.rb +1 -0
  5. data/lib/blather/core_ext/eventmachine.rb +122 -0
  6. data/lib/blather/errors/stanza_error.rb +1 -0
  7. data/lib/blather/file_transfer.rb +100 -0
  8. data/lib/blather/file_transfer/ibb.rb +68 -0
  9. data/lib/blather/file_transfer/s5b.rb +104 -0
  10. data/lib/blather/roster_item.rb +2 -2
  11. data/lib/blather/stanza/disco/disco_info.rb +17 -2
  12. data/lib/blather/stanza/iq/ibb.rb +83 -0
  13. data/lib/blather/stanza/iq/s5b.rb +205 -0
  14. data/lib/blather/stanza/iq/si.rb +410 -0
  15. data/lib/blather/stanza/iq/vcard.rb +147 -0
  16. data/lib/blather/stanza/message.rb +10 -2
  17. data/lib/blather/stanza/presence/status.rb +11 -3
  18. data/lib/blather/stanza/pubsub.rb +3 -1
  19. data/lib/blather/stanza/pubsub/event.rb +18 -0
  20. data/lib/blather/stanza/pubsub/subscriptions.rb +4 -2
  21. data/lib/blather/stanza/pubsub/unsubscribe.rb +17 -1
  22. data/lib/blather/stanza/x.rb +12 -2
  23. data/lib/blather/stream/parser.rb +1 -0
  24. data/lib/test.rb +55 -0
  25. data/spec/blather/client/client_spec.rb +18 -4
  26. data/spec/blather/client/dsl/pubsub_spec.rb +12 -2
  27. data/spec/blather/client/dsl_spec.rb +1 -1
  28. data/spec/blather/core_ext/nokogiri_spec.rb +1 -1
  29. data/spec/blather/errors/sasl_error_spec.rb +1 -1
  30. data/spec/blather/errors/stanza_error_spec.rb +1 -1
  31. data/spec/blather/errors/stream_error_spec.rb +1 -1
  32. data/spec/blather/errors_spec.rb +1 -1
  33. data/spec/blather/file_transfer_spec.rb +100 -0
  34. data/spec/blather/jid_spec.rb +1 -1
  35. data/spec/blather/roster_item_spec.rb +32 -2
  36. data/spec/blather/roster_spec.rb +1 -1
  37. data/spec/blather/stanza/discos/disco_info_spec.rb +12 -3
  38. data/spec/blather/stanza/discos/disco_items_spec.rb +1 -1
  39. data/spec/blather/stanza/iq/command_spec.rb +1 -1
  40. data/spec/blather/stanza/iq/ibb_spec.rb +136 -0
  41. data/spec/blather/stanza/iq/query_spec.rb +1 -1
  42. data/spec/blather/stanza/iq/roster_spec.rb +1 -1
  43. data/spec/blather/stanza/iq/s5b_spec.rb +60 -0
  44. data/spec/blather/stanza/iq/si_spec.rb +101 -0
  45. data/spec/blather/stanza/iq/vcard_spec.rb +96 -0
  46. data/spec/blather/stanza/iq_spec.rb +1 -1
  47. data/spec/blather/stanza/message_spec.rb +48 -2
  48. data/spec/blather/stanza/presence/status_spec.rb +1 -1
  49. data/spec/blather/stanza/presence/subscription_spec.rb +1 -1
  50. data/spec/blather/stanza/presence_spec.rb +1 -1
  51. data/spec/blather/stanza/pubsub/affiliations_spec.rb +2 -2
  52. data/spec/blather/stanza/pubsub/create_spec.rb +2 -2
  53. data/spec/blather/stanza/pubsub/event_spec.rb +16 -2
  54. data/spec/blather/stanza/pubsub/items_spec.rb +2 -2
  55. data/spec/blather/stanza/pubsub/publish_spec.rb +2 -2
  56. data/spec/blather/stanza/pubsub/retract_spec.rb +2 -2
  57. data/spec/blather/stanza/pubsub/subscribe_spec.rb +2 -2
  58. data/spec/blather/stanza/pubsub/subscription_spec.rb +2 -2
  59. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +3 -3
  60. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +15 -2
  61. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +2 -2
  62. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +2 -2
  63. data/spec/blather/stanza/pubsub_owner_spec.rb +2 -2
  64. data/spec/blather/stanza/pubsub_spec.rb +14 -2
  65. data/spec/blather/stanza/x_spec.rb +1 -1
  66. data/spec/blather/stanza_spec.rb +1 -1
  67. data/spec/blather/stream/client_spec.rb +1 -1
  68. data/spec/blather/stream/component_spec.rb +1 -1
  69. data/spec/blather/stream/parser_spec.rb +11 -1
  70. data/spec/blather/xmpp_node_spec.rb +1 -1
  71. data/spec/fixtures/pubsub.rb +2 -2
  72. data/spec/spec_helper.rb +27 -0
  73. metadata +85 -23
data/lib/blather.rb CHANGED
@@ -8,12 +8,16 @@
8
8
  logger
9
9
 
10
10
  blather/core_ext/active_support
11
+ blather/core_ext/eventmachine
11
12
  blather/core_ext/nokogiri
12
13
 
13
14
  blather/errors
14
15
  blather/errors/sasl_error
15
16
  blather/errors/stanza_error
16
17
  blather/errors/stream_error
18
+ blather/file_transfer
19
+ blather/file_transfer/ibb
20
+ blather/file_transfer/s5b
17
21
  blather/jid
18
22
  blather/roster
19
23
  blather/roster_item
@@ -24,6 +28,10 @@
24
28
  blather/stanza/iq/query
25
29
  blather/stanza/iq/command
26
30
  blather/stanza/iq/roster
31
+ blather/stanza/iq/ibb
32
+ blather/stanza/iq/s5b
33
+ blather/stanza/iq/si
34
+ blather/stanza/iq/vcard
27
35
  blather/stanza/disco
28
36
  blather/stanza/disco/disco_info
29
37
  blather/stanza/disco/disco_items
@@ -112,9 +112,17 @@ module Blather
112
112
  @tmp_handlers[id.to_s] = handler
113
113
  end
114
114
 
115
+ # Clear handlers with given guards
116
+ #
117
+ # @param [Symbol, nil] type remove filters for a specific handler
118
+ # @param [guards] guards take a look at the guards documentation
119
+ def clear_handlers(type, *guards)
120
+ @handlers[type].delete_if { |g, _| g == guards }
121
+ end
122
+
115
123
  # Register a handler
116
124
  #
117
- # @param [Symbol, nil] handler set the filter on a specific handler
125
+ # @param [Symbol, nil] type set the filter on a specific handler
118
126
  # @param [guards] guards take a look at the guards documentation
119
127
  # @yield [Blather::Stanza] stanza the incomming stanza
120
128
  def register_handler(type, *guards, &handler)
@@ -87,9 +87,9 @@ module DSL
87
87
  # Defaults to the stripped current JID
88
88
  # @param [#to_s] host the PubSub host (defaults to the initialized host)
89
89
  # @yield [Blather::Stanza] stanza the reply stanza
90
- def unsubscribe(node, jid = nil, host = nil)
90
+ def unsubscribe(node, jid = nil, subid = nil, host = nil)
91
91
  jid ||= client.jid.stripped
92
- stanza = Stanza::PubSub::Unsubscribe.new(:set, send_to(host), node, jid)
92
+ stanza = Stanza::PubSub::Unsubscribe.new(:set, send_to(host), node, jid, subid)
93
93
  request(stanza) { |n| yield n if block_given? }
94
94
  end
95
95
 
@@ -36,6 +36,7 @@ end
36
36
 
37
37
  class Symbol # @private
38
38
  def duplicable?; false; end
39
+ def to_proc; proc { |obj, *args| obj.send(self, *args) }; end
39
40
  end
40
41
 
41
42
  class Numeric # @private
@@ -0,0 +1,122 @@
1
+ module EventMachine
2
+ module Protocols
3
+ # Basic SOCKS v5 client implementation
4
+ #
5
+ # Use as you would any regular connection:
6
+ #
7
+ # class MyConn < EM::P::Socks5
8
+ # def post_init
9
+ # send_data("sup")
10
+ # end
11
+ #
12
+ # def receive_data(data)
13
+ # send_data("you said: #{data}")
14
+ # end
15
+ # end
16
+ #
17
+ # EM.connect socks_host, socks_port, MyConn, host, port
18
+ #
19
+ class Socks5 < Connection
20
+ def initialize(host, port)
21
+ @host = host
22
+ @port = port
23
+ @socks_error_code = nil
24
+ @buffer = ''
25
+ @socks_state = :method_negotiation
26
+ @socks_methods = [0] # TODO: other authentication methods
27
+ setup_methods
28
+ end
29
+
30
+ def setup_methods
31
+ class << self
32
+ def post_init; socks_post_init; end
33
+ def receive_data(*a); socks_receive_data(*a); end
34
+ end
35
+ end
36
+
37
+ def restore_methods
38
+ class << self
39
+ remove_method :post_init
40
+ remove_method :receive_data
41
+ end
42
+ end
43
+
44
+ def socks_post_init
45
+ packet = [5, @socks_methods.size].pack('CC') + @socks_methods.pack('C*')
46
+ send_data(packet)
47
+ end
48
+
49
+ def socks_receive_data(data)
50
+ @buffer << data
51
+
52
+ if @socks_state == :method_negotiation
53
+ return if @buffer.size < 2
54
+
55
+ header_resp = @buffer.slice! 0, 2
56
+ _, method_code = header_resp.unpack("cc")
57
+
58
+ if @socks_methods.include?(method_code)
59
+ @socks_state = :connecting
60
+ packet = [5, 1, 0].pack("C*")
61
+
62
+ if @host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # IPv4
63
+ packet << [1, $1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*")
64
+ elsif @host.include?(":") # IPv6
65
+ l, r = if @host =~ /^(.*)::(.*)$/
66
+ [$1,$2].map {|i| i.split ":"}
67
+ else
68
+ [@host.split(":"),[]]
69
+ end
70
+ dec_groups = (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
71
+ packet << ([4] + dec_groups).pack("Cn8")
72
+ else # Domain
73
+ packet << [3, @host.length, @host].pack("CCA*")
74
+ end
75
+ packet << [@port].pack("n")
76
+
77
+ send_data packet
78
+ else
79
+ @socks_state = :invalid
80
+ @socks_error_code = method_code
81
+ close_connection
82
+ return
83
+ end
84
+ elsif @socks_state == :connecting
85
+ return if @buffer.size < 4
86
+
87
+ header_resp = @buffer.slice! 0, 4
88
+ _, response_code, _, address_type = header_resp.unpack("C*")
89
+
90
+ if response_code == 0
91
+ case address_type
92
+ when 1
93
+ @buffer.slice! 0, 4
94
+ when 3
95
+ len = @buffer.slice! 0, 1
96
+ @buffer.slice! 0, len.unpack("C").first
97
+ when 4
98
+ @buffer.slice! 0, 16
99
+ else
100
+ @socks_state = :invalid
101
+ @socks_error_code = address_type
102
+ close_connection
103
+ return
104
+ end
105
+ @buffer.slice! 0, 2
106
+
107
+ @socks_state = :connected
108
+ restore_methods
109
+
110
+ post_init
111
+ receive_data(@buffer) unless @buffer.empty?
112
+ else
113
+ @socks_state = :invalid
114
+ @socks_error_code = response_code
115
+ close_connection
116
+ return
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -77,6 +77,7 @@ class StanzaError < BlatherError
77
77
  node << (error_node = XMPPNode.new('error'))
78
78
 
79
79
  error_node << (err = XMPPNode.new(@name, error_node.document))
80
+ error_node['type'] = self.type
80
81
  err.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
81
82
 
82
83
  if self.text
@@ -0,0 +1,100 @@
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
+ # Create a new FileTransfer
13
+ #
14
+ # @param [Blather::Stream] stream the stream the file transfer should use
15
+ # @param [Blather::Stanza::Iq::Si] iq a si iq used to stream-initiation
16
+ def initialize(stream, iq = nil)
17
+ @stream = stream
18
+ @allow_s5b = true
19
+ @allow_ibb = true
20
+
21
+ @iq = iq
22
+ end
23
+
24
+ # Accept an incoming file-transfer
25
+ #
26
+ # @param [module] handler the handler for incoming data, see Blather::FileTransfer::SimpleFileReceiver for an example
27
+ # @param [Array] params the params to be passed into the handler
28
+ def accept(handler, *params)
29
+ answer = @iq.reply
30
+
31
+ answer.si.feature.x.type = :submit
32
+
33
+ supported_methods = @iq.si.feature.x.field("stream-method").options.map(&:value)
34
+ if supported_methods.include?(Stanza::Iq::S5b::NS_S5B) and @allow_s5b
35
+ answer.si.feature.x.fields = {:var => 'stream-method', :value => Stanza::Iq::S5b::NS_S5B}
36
+
37
+ @stream.register_handler :s5b_open, :from => @iq.from do |iq|
38
+ transfer = Blather::FileTransfer::S5b.new(@stream, iq)
39
+ transfer.allow_ibb_fallback = true if @allow_ibb
40
+ transfer.accept(handler, *params)
41
+ true
42
+ end
43
+
44
+ @stream.write answer
45
+ elsif supported_methods.include?(Stanza::Iq::Ibb::NS_IBB) and @allow_ibb
46
+ answer.si.feature.x.fields = {:var => 'stream-method', :value => Stanza::Iq::Ibb::NS_IBB}
47
+
48
+ @stream.register_handler :ibb_open, :from => @iq.from do |iq|
49
+ transfer = Blather::FileTransfer::Ibb.new(@stream, iq)
50
+ transfer.accept(handler, *params)
51
+ true
52
+ end
53
+
54
+ @stream.write answer
55
+ else
56
+ reason = XMPPNode.new('no-valid-streams')
57
+ reason.namespace = Blather::Stanza::Iq::Si::NS_SI
58
+
59
+ @stream.write StanzaError.new(@iq, 'bad-request', 'cancel', nil, [reason]).to_node
60
+ end
61
+ end
62
+
63
+ # Decline an incoming file-transfer
64
+ def decline
65
+ answer = StanzaError.new(@iq, 'forbidden', 'cancel', 'Offer declined').to_node
66
+
67
+ @stream.write answer
68
+ end
69
+
70
+ # Offer a file to somebody, not implemented yet
71
+ def offer
72
+ # TODO: implement
73
+ end
74
+
75
+ # Simple handler for incoming file transfers
76
+ #
77
+ # You can define your own handler and pass it to the accept method.
78
+ module SimpleFileReceiver
79
+ def initialize(path, size)
80
+ @path = path
81
+ @size = size
82
+ @transferred = 0
83
+ end
84
+
85
+ def post_init
86
+ @file = File.open(@path, "w")
87
+ end
88
+
89
+ def receive_data(data)
90
+ @transferred += data.size
91
+ @file.write data
92
+ end
93
+
94
+ def unbind
95
+ @file.close
96
+ File.delete(@path) unless @transferred == @size
97
+ end
98
+ end
99
+ end
100
+ 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,104 @@
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
+ def initialize(stream, iq)
11
+ @stream = stream
12
+ @iq = iq
13
+ @allow_ibb_fallback = true
14
+ end
15
+
16
+ # Accept an incoming file-transfer
17
+ #
18
+ # @param [module] handler the handler for incoming data, see Blather::FileTransfer::SimpleFileReceiver for an example
19
+ # @param [Array] params the params to be passed into the handler
20
+ def accept(handler, *params)
21
+ @streamhosts = @iq.streamhosts
22
+ @socket_address = Digest::SHA1.hexdigest("#{@iq.sid}#{@iq.from}#{@iq.to}")
23
+
24
+ @handler = handler
25
+ @params = params
26
+
27
+ connect_next_streamhost
28
+ @stream.clear_handlers :s5b_open, :from => @iq.from
29
+ end
30
+
31
+ # Decline an incoming file-transfer
32
+ def decline
33
+ @stream.clear_handlers :s5b_open, :from => @iq.from
34
+ @stream.write StanzaError.new(@iq, 'not-acceptable', :auth).to_node
35
+ end
36
+
37
+ # Offer a file to somebody, not implemented yet
38
+ def offer
39
+ # TODO: implement
40
+ end
41
+
42
+ private
43
+
44
+ def connect_next_streamhost
45
+ if streamhost = @streamhosts.shift
46
+ connect(streamhost)
47
+ else
48
+ if @allow_ibb_fallback
49
+ @stream.register_handler :ibb_open, :from => @iq.from, :sid => @iq.sid do |iq|
50
+ transfer = Blather::FileTransfer::Ibb.new(@stream, iq)
51
+ transfer.accept(@handler, *@params)
52
+ true
53
+ end
54
+ end
55
+
56
+ @stream.write StanzaError.new(@iq, 'item-not-found', :cancel).to_node
57
+ end
58
+ end
59
+
60
+ def connect(streamhost)
61
+ begin
62
+ socket = EM.connect streamhost.host, streamhost.port, SocketConnection, @socket_address, 0, @handler, *@params
63
+
64
+ socket.callback do
65
+ answer = @iq.reply
66
+ answer.streamhosts = nil
67
+ answer.streamhost_used = streamhost.jid
68
+
69
+ @stream.write answer
70
+ end
71
+
72
+ socket.errback do
73
+ connect_next_streamhost
74
+ end
75
+ rescue EventMachine::ConnectionError => e
76
+ connect_next_streamhost
77
+ end
78
+ end
79
+
80
+ class SocketConnection < EM::P::Socks5
81
+ include EM::Deferrable
82
+
83
+ def initialize(host, port, handler, *params)
84
+ super(host, port)
85
+ @@handler = handler
86
+ @params = params
87
+ end
88
+
89
+ def post_init
90
+ self.succeed
91
+ class << self
92
+ include @@handler
93
+ end
94
+ send(:initialize, *@params)
95
+ post_init
96
+ end
97
+
98
+ def unbind
99
+ self.fail if @socks_state != :connected
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end