blather 0.4.14 → 0.4.15

Sign up to get free protection for your applications and to get access to all the features.
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