cancer 0.1.0.a1

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 (89) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/cancer.gemspec +156 -0
  7. data/documentation/STREAM_INITIATION.markdown +18 -0
  8. data/examples/example.rb +80 -0
  9. data/examples/example_2.rb +20 -0
  10. data/examples/example_em.rb +11 -0
  11. data/examples/example_em_helper.rb +23 -0
  12. data/examples/example_im_roster.rb +26 -0
  13. data/examples/example_xep_0004.rb +124 -0
  14. data/examples/example_xep_0047.rb +35 -0
  15. data/examples/example_xep_0050.rb +78 -0
  16. data/examples/example_xep_0065.rb +66 -0
  17. data/examples/example_xep_0096.dup.rb +40 -0
  18. data/examples/example_xep_0096_xep_0047.rb +42 -0
  19. data/examples/example_xep_0096_xep_0065.rb +40 -0
  20. data/lib/cancer.rb +122 -0
  21. data/lib/cancer/adapter.rb +33 -0
  22. data/lib/cancer/adapters/em.rb +88 -0
  23. data/lib/cancer/adapters/socket.rb +122 -0
  24. data/lib/cancer/builder.rb +28 -0
  25. data/lib/cancer/controller.rb +32 -0
  26. data/lib/cancer/dependencies.rb +187 -0
  27. data/lib/cancer/events/abstract_event.rb +10 -0
  28. data/lib/cancer/events/base_matchers.rb +63 -0
  29. data/lib/cancer/events/binary_matchers.rb +30 -0
  30. data/lib/cancer/events/eventable.rb +92 -0
  31. data/lib/cancer/events/exception_events.rb +28 -0
  32. data/lib/cancer/events/handler.rb +33 -0
  33. data/lib/cancer/events/manager.rb +130 -0
  34. data/lib/cancer/events/named_events.rb +25 -0
  35. data/lib/cancer/events/proc_matcher.rb +16 -0
  36. data/lib/cancer/events/xml_events.rb +44 -0
  37. data/lib/cancer/exceptions.rb +53 -0
  38. data/lib/cancer/jid.rb +215 -0
  39. data/lib/cancer/lock.rb +32 -0
  40. data/lib/cancer/spec.rb +35 -0
  41. data/lib/cancer/spec/error.rb +18 -0
  42. data/lib/cancer/spec/extentions.rb +79 -0
  43. data/lib/cancer/spec/matcher.rb +30 -0
  44. data/lib/cancer/spec/mock_adapter.rb +139 -0
  45. data/lib/cancer/spec/mock_stream.rb +15 -0
  46. data/lib/cancer/spec/transcript.rb +107 -0
  47. data/lib/cancer/stream.rb +182 -0
  48. data/lib/cancer/stream/builder.rb +20 -0
  49. data/lib/cancer/stream/controller.rb +36 -0
  50. data/lib/cancer/stream/event_helper.rb +12 -0
  51. data/lib/cancer/stream/xep.rb +52 -0
  52. data/lib/cancer/stream_parser.rb +144 -0
  53. data/lib/cancer/support.rb +27 -0
  54. data/lib/cancer/support/hash.rb +32 -0
  55. data/lib/cancer/support/string.rb +22 -0
  56. data/lib/cancer/synchronized_stanza.rb +79 -0
  57. data/lib/cancer/thread_pool.rb +118 -0
  58. data/lib/cancer/xep.rb +43 -0
  59. data/lib/cancer/xeps/core.rb +109 -0
  60. data/lib/cancer/xeps/core/bind.rb +33 -0
  61. data/lib/cancer/xeps/core/sasl.rb +113 -0
  62. data/lib/cancer/xeps/core/session.rb +22 -0
  63. data/lib/cancer/xeps/core/stream.rb +18 -0
  64. data/lib/cancer/xeps/core/terminator.rb +21 -0
  65. data/lib/cancer/xeps/core/tls.rb +34 -0
  66. data/lib/cancer/xeps/im.rb +323 -0
  67. data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
  68. data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
  69. data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
  70. data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
  71. data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
  72. data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
  73. data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
  74. data/lib/cancer/xeps/xep_0095_si.rb +211 -0
  75. data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
  76. data/lib/cancer/xeps/xep_0114_component.rb +73 -0
  77. data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
  78. data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
  79. data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
  80. data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
  81. data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
  82. data/spec/spec.opts +2 -0
  83. data/spec/spec_helper.rb +14 -0
  84. data/spec/stream/stanza_errors_spec.rb +47 -0
  85. data/spec/stream/stream_errors_spec.rb +38 -0
  86. data/spec/stream/stream_initialization_spec.rb +160 -0
  87. data/spec/xep_0050/local_spec.rb +165 -0
  88. data/spec/xep_0050/remote_spec.rb +44 -0
  89. metadata +200 -0
@@ -0,0 +1,256 @@
1
+
2
+ module Cancer
3
+ # Ad-Hoc Commands
4
+ # http://xmpp.org/extensions/xep-0050.html
5
+ module XEP_0050
6
+ include Cancer::XEP
7
+
8
+ NS = "http://jabber.org/protocol/commands"
9
+ NAMESPACES = { 'c' => CLIENT_NS, 'cmd' => NS, 'x' => Cancer::XEP_0004::NS }
10
+
11
+ dependency 'core'
12
+ dependency 'xep-0004'
13
+ dependency 'xep-0030'
14
+
15
+ def self.enhance_stream(stream)
16
+ stream.extend_stream do
17
+ include Cancer::XEP_0050::StreamHelpers
18
+ end
19
+ stream.install_controller Cancer::XEP_0050::Controller
20
+ stream.install_event_helper Cancer::XEP_0050::EventDSL
21
+ stream.disco.feature(NS)
22
+ end
23
+
24
+ module StreamHelpers
25
+
26
+ def commands_provided_by(peer)
27
+ disco_items_for(peer, NS).inject({}) { |commands, item| commands[item['node'].to_s] = item['name'].to_s ; commands }
28
+ end
29
+
30
+ def command(to, node)
31
+ session = RemoteSession.new(self, to, node)
32
+ yield(session) if block_given?
33
+ session
34
+ end
35
+
36
+ def register_command(uri, name=nil)
37
+ @disco_commands ||= disco.item(:node => NS, :jid => self.options[:jid])
38
+ @disco_commands.item(:node => uri, :name => name, :jid => self.options[:jid])
39
+ end
40
+
41
+ end
42
+
43
+ class RemoteSession
44
+ attr_reader :peer, :node, :id, :status, :allowed_actions, :execute_action
45
+
46
+ def initialize(stream, peer, node)
47
+ @stream, @peer, @node = stream, peer, node
48
+ @status = @id = nil
49
+ @allowed_actions = [:execute]
50
+ @execute_action = :execute
51
+ @stages = []
52
+ end
53
+
54
+ def send(action, form=nil)
55
+ unless @allowed_actions.include?(action.to_sym)
56
+ raise "Action #{action} is not allowed (#{@allowed_actions.join(', ')})"
57
+ end
58
+
59
+ attributes = {:xmlns => NS, :node => @node, :action => action}
60
+ attributes[:sessionid] = @id if @id
61
+ result = @stream.send_iq(@peer, :set) do |x|
62
+ x.command(attributes) do
63
+ x.x(form) if form
64
+ end
65
+ end
66
+
67
+ command_node = result.first('/c:iq/cmd:command', NAMESPACES)
68
+ actions_node = command_node.all('./cmd:actions', NAMESPACES)
69
+ form_node = command_node.all('./x:x', NAMESPACES)
70
+
71
+ @id = command_node[:sessionid].to_s if command_node[:sessionid]
72
+ @status = (command_node[:status] || :completed).to_sym
73
+ @allowed_actions = actions_node.children.collect { |n| n.name.to_sym }
74
+ @execute_action = (actions_node[:execute] || :next).to_sym
75
+
76
+ form = Cancer::XEP_0004::Form.load(form_node)
77
+ @stages << form
78
+
79
+ form
80
+ end
81
+
82
+ def execute!(form=nil)
83
+ send(@execute_action, form)
84
+ end
85
+
86
+ def next!(form=nil)
87
+ send(:next, form)
88
+ end
89
+
90
+ def complete!(form=nil)
91
+ send(:complete, form)
92
+ end
93
+
94
+ def prev!
95
+ send(:prev)
96
+ end
97
+
98
+ def cancel!
99
+ send(:cancel)
100
+ end
101
+ end
102
+
103
+ class LocalSession
104
+ attr_reader :peer, :node, :id, :current_stage, :stages
105
+ attr_accessor :next_stage, :iq_id
106
+
107
+ def initialize(controller, peer, node, id, initial_stage=:initial)
108
+ @controller = controller
109
+ @peer, @node, @id = peer, node, id
110
+ @current_stage = nil
111
+ @next_stage = initial_stage.to_sym
112
+ @stages = {}
113
+ @iq_id = nil
114
+ end
115
+
116
+ def complete!
117
+ @controller.send_iq(@peer, :result, @iq_id) do |x|
118
+ x.command(:xmlns => NS, :node => @node, :sessionid => @id, :status => 'completed') do
119
+ yield x
120
+ end
121
+ end
122
+ end
123
+
124
+ def next!(next_stage, form=nil, options={})
125
+ options, form = form, nil if Hash === form
126
+
127
+ @next_stage = next_stage.to_sym
128
+ @controller.send_iq(@peer, :result, @iq_id) do |x|
129
+ x.command(:xmlns => NS, :node => @node, :sessionid => @id, :status => 'executing') do
130
+
131
+ x.actions(options[:execute] ? {:execute => options[:execute]} : {}) do
132
+ (options[:actions] || []).each do |action|
133
+ x.send(action)
134
+ end
135
+ end
136
+
137
+ if form
138
+ form = case form
139
+ when Cancer::XEP_0004::Form then form
140
+ when Cancer::XEP_0004::FormDefinition then form
141
+ else @controller.form(form)
142
+ end
143
+
144
+ x.x(form, options.merge(:verbose => true))
145
+ end
146
+
147
+ end
148
+ end
149
+ end
150
+
151
+ def form
152
+ @stages[@current_stage]
153
+ end
154
+
155
+ def push_stage!(iq_id, form_data)
156
+ @iq_id = iq_id
157
+ @stages[@next_stage] = (form_data ? Cancer::XEP_0004::Form.load(form_data) : nil)
158
+ @current_stage = @next_stage
159
+ @next_stage = nil
160
+ end
161
+ end
162
+
163
+ class Controller < Cancer::Controller
164
+
165
+ on { |e| e.xpath('/c:iq[@type="set"]/cmd:command', NAMESPACES) }
166
+ def handle_command(e)
167
+ command_node = e.xml.first('/c:iq/cmd:command', NAMESPACES)
168
+ session_id = (command_node[:sessionid] || rand(1<<100)).to_s
169
+ session = command_sessions[session_id]
170
+
171
+ unless session
172
+ session = LocalSession.new(self, e.sender, command_node[:node].to_s, session_id)
173
+ command_sessions[session_id] = session
174
+ end
175
+
176
+ session.push_stage!(e.id, e.xml.first('/c:iq/cmd:command/x:x', NAMESPACES))
177
+
178
+ fire! Cancer::XEP_0050::Event, self, session, command_node[:action]
179
+ end
180
+
181
+ def command_sessions
182
+ @command_sessions ||= {}
183
+ end
184
+
185
+ end
186
+
187
+ class Event < Cancer::Events::AbstractEvent
188
+ attr_reader :session, :action
189
+ def initialize(controller, session, action)
190
+ @controller, @session, @action = controller, session, action
191
+ @action = @action.to_sym if @action
192
+ end
193
+ end
194
+
195
+ class EventMatcher < Cancer::Events::Matcher
196
+
197
+ def initialize(node, options={})
198
+ @node = node.to_s
199
+ @stage = (options.delete(:stage) || :initial).to_sym
200
+ @action = (options.delete(:action) || :execute).to_sym
201
+ @default_action = (options.delete(:default_action) || :execute).to_sym
202
+ end
203
+
204
+ def match?(event)
205
+ (
206
+ Cancer::XEP_0050::Event === event and
207
+ matching_node?(event) and
208
+ matching_stage?(event) and
209
+ matching_action?(event)
210
+ )
211
+ end
212
+
213
+ def matching_node?(event)
214
+ @node ? (@node == event.session.node) : true
215
+ end
216
+
217
+ def matching_stage?(event)
218
+ @stage ? (@stage == event.session.current_stage) : true
219
+ end
220
+
221
+ def matching_action?(event)
222
+ @action ? (@action == (event.action || @default_action)) : true
223
+ end
224
+
225
+ end
226
+
227
+ module EventDSL
228
+
229
+ def command(node, options={})
230
+ Cancer::XEP_0050::EventMatcher.new(node, options)
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+ end
237
+
238
+ =begin
239
+ class Controller
240
+
241
+ on { |e| e.command(:config) }
242
+ def config_initial_stage(e)
243
+ if ok? current_stage
244
+ e.session.next!(:ask_details, 'my_form', default_values)
245
+ else
246
+ # error
247
+ end
248
+ end
249
+
250
+ on { |e| e.command(:config, :stage => :ask_details) }
251
+ def config_ask_details_stage(e)
252
+
253
+ end
254
+
255
+ end
256
+ =end
@@ -0,0 +1,306 @@
1
+
2
+ require 'ipaddr'
3
+ require 'digest/sha1'
4
+
5
+ module Cancer
6
+ # SOCKS5 Bytestreams
7
+ # http://xmpp.org/extensions/xep-0065.html
8
+ module XEP_0065
9
+ include Cancer::XEP
10
+
11
+ dependency 'core'
12
+ dependency 'xep-0030'
13
+ dependency 'xep-0095', :weak => true
14
+
15
+ NS = 'http://jabber.org/protocol/bytestreams'
16
+
17
+ def self.enhance_stream(stream)
18
+ stream.extend_stream do
19
+ include Cancer::XEP_0065::StreamHelpers
20
+ end
21
+ stream.install_controller Cancer::XEP_0065::Controller
22
+ stream.install_event_helper Cancer::XEP_0065::EventDSL
23
+ if stream.respond_to?(:register_stream_method)
24
+ stream.register_stream_method(Cancer::XEP_0065::StreamMethod, Cancer::XEP_0065::NS)
25
+ stream.install_controller Cancer::XEP_0065::SOCKS5Controller
26
+ end
27
+ stream.disco.feature NS
28
+ end
29
+
30
+ module StreamHelpers
31
+
32
+ def open_socks5_bytestream(to, sid=nil)
33
+ sid ||= rand(1<<100).to_s
34
+
35
+ streamhosts = discover_streamhosts
36
+
37
+ result = send_iq(to, :set) do |x|
38
+ x.query(:xmlns => Cancer::XEP_0065::NS, :sid => sid, :mode => 'tcp') do
39
+
40
+ streamhosts.each do |jid, options|
41
+ x.streamhost(options.merge(:jid => jid))
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ namespaces = { 'c' => CLIENT_NS, 's' => Cancer::XEP_0065::NS }
48
+ streamhost = result.first("/c:iq/s:query/s:streamhost-used", namespaces)
49
+ if streamhost
50
+ streamhost_jid = streamhost[:jid].to_s
51
+ streamhost = streamhosts[streamhost_jid]
52
+
53
+ address_base = "#{sid}#{result[:to]}#{result[:from]}"
54
+ address = Digest::SHA1.hexdigest(address_base)
55
+
56
+ socket = TCPSocket.new(streamhost[:host], streamhost[:port])
57
+ socket.extend Cancer::XEP_0065::TCPSocketHelper
58
+ socket.extend Cancer::XEP_0065::SOCKS_5
59
+ socket.auth
60
+ socket.connect_domain(address, 0)
61
+
62
+ send_iq(streamhost_jid, :set) do |x|
63
+ x.query(:xmlns => Cancer::XEP_0065::NS, :sid => sid) do
64
+ x.activate(result[:from].to_s)
65
+ end
66
+ end
67
+
68
+ socket.peer = result[:from].to_s
69
+ socket.stream_id = sid
70
+ socket.streamhost = streamhost_jid
71
+
72
+ if block_given?
73
+ yield(socket)
74
+ socket.close
75
+ else
76
+ socket
77
+ end
78
+ else
79
+ return false
80
+ end
81
+ end
82
+
83
+ def discover_streamhosts(server_jid=nil)
84
+ @streamhosts ||= {}
85
+ server_jid ||= options[:jid].server_jid
86
+ @streamhosts[server_jid.to_s] ||= begin
87
+ discover_socks5_proxies(server_jid).inject({}) do |streamhosts, proxy|
88
+ streamhosts.merge discover_address_for_socks5_proxy(proxy)
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def discover_socks5_proxies(server_jid)
96
+ proxies = []
97
+
98
+ disco_items_for(server_jid).each do |item|
99
+ info = disco_info_for(item['jid'].to_s)
100
+ has_identity = info.identities.any? { |identity| identity[:category] == 'proxy' }
101
+ has_feature = info.features.include?(Cancer::XEP_0065::NS)
102
+ proxies.push(item['jid'].to_s) if has_identity and has_feature
103
+ end
104
+
105
+ proxies
106
+ end
107
+
108
+ def discover_address_for_socks5_proxy(jid, sid=nil)
109
+ attributes = { :xmlns => Cancer::XEP_0065::NS }
110
+ attributes[:sid] = sid if sid
111
+ result = send_iq(jid) do |x|
112
+ x.query(attributes)
113
+ end
114
+
115
+ streamhosts = {}
116
+
117
+ namespaces = { 'c' => CLIENT_NS, 's' => Cancer::XEP_0065::NS }
118
+ result.all('/c:iq/s:query/s:streamhost', namespaces) do |streamhost|
119
+ streamhosts[streamhost[:jid].to_s] = {
120
+ :host => streamhost[:host],
121
+ :port => (streamhost[:port].to_i rescue 1080),
122
+ :zeroconf => streamhost[:zeroconf],
123
+ }.clean
124
+ end
125
+
126
+ streamhosts
127
+ end
128
+
129
+ end
130
+
131
+ class Controller < Cancer::Controller
132
+
133
+ NAMESPACES = { 'c' => CLIENT_NS, 'bs' => NS }
134
+
135
+ on { |e| e.xpath('/c:iq/bs:query/bs:streamhost', NAMESPACES) }
136
+ def open_bytestream(e)
137
+ query = e.xml.first('/c:iq/bs:query', NAMESPACES)
138
+
139
+ streamhosts = {}
140
+ e.xml.all('/c:iq/bs:query/bs:streamhost', NAMESPACES) do |streamhost|
141
+ streamhosts[streamhost[:jid].to_s] = {
142
+ :host => streamhost[:host],
143
+ :port => (streamhost[:port].to_i rescue 1080),
144
+ :zeroconf => streamhost[:zeroconf],
145
+ }.clean
146
+ end
147
+
148
+ fire!(Event, self,
149
+ :streamhosts => streamhosts,
150
+ :iq_id => e.id,
151
+ :peer => e.sender,
152
+ :self => e.receiver,
153
+ :mode => query[:mode],
154
+ :sid => query[:sid]
155
+ )
156
+ end
157
+
158
+ end
159
+
160
+ class Event < Cancer::Events::AbstractEvent
161
+ attr_reader :bytestream, :options
162
+ def initialize(controller, options)
163
+ @controller, @bytestream, @options = controller, nil, options
164
+ end
165
+
166
+ def accept!(&proc)
167
+ if block_given?
168
+ @controller.enqueue(self, proc) do |e, proc|
169
+ bs = e.connect_with_socks5_proxy_and_handle
170
+ proc.call(bs) if bs
171
+ end
172
+ else
173
+ bs = self.connect_with_socks5_proxy_and_handle
174
+ proc.call(bs) if bs
175
+ end
176
+ end
177
+
178
+ def reject!
179
+ @controller.send_iq(@options[:peer], :error, @options[:iq_id]) do |x|
180
+ x.error(:code => 406, :type => 'auth') do
181
+ x.send('not-acceptable', :xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas')
182
+ end
183
+ end
184
+ end
185
+
186
+ def connect_with_socks5_proxy_and_handle
187
+ @bytestream = connect_with_socks5_proxy
188
+
189
+ if @bytestream
190
+ @controller.send_iq(@options[:peer], :result, @options[:iq_id]) do |x|
191
+ x.query(:xmlns => Cancer::XEP_0065::NS, :sid => @options[:sid]) do
192
+ x.send('streamhost-used', :jid => @bytestream.streamhost)
193
+ end
194
+ end
195
+ else
196
+ @controller.send_iq(@options[:peer], :error, @options[:iq_id]) do |x|
197
+ x.error(:code => 404, :type => 'cancel') do
198
+ x.send('item-not-found', :xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas')
199
+ end
200
+ end
201
+ return false
202
+ end
203
+
204
+ return @bytestream
205
+ end
206
+
207
+ private
208
+
209
+ def connect_with_socks5_proxy
210
+ @options[:streamhosts].each do |jid, streamhost|
211
+
212
+ begin
213
+
214
+ address_base = "#{@options[:sid]}#{@options[:peer]}#{@options[:self]}"
215
+ address = Digest::SHA1.hexdigest(address_base)
216
+
217
+ socket = TCPSocket.new(streamhost[:host].to_s, streamhost[:port])
218
+ socket.extend Cancer::XEP_0065::TCPSocketHelper
219
+ socket.extend Cancer::XEP_0065::SOCKS_5
220
+ socket.auth
221
+ socket.connect_domain(address, 0)
222
+
223
+ socket.peer = @options[:from].to_s
224
+ socket.stream_id = @options[:sid]
225
+ socket.streamhost = jid.to_s
226
+
227
+ return socket
228
+ rescue IOError => e
229
+ Cancer.logger.debug e
230
+ end
231
+ end
232
+ return nil
233
+ end
234
+
235
+ end
236
+
237
+ class EventMatcher < Cancer::Events::Matcher
238
+ def match?(event)
239
+ Cancer::XEP_0065::Event === event
240
+ end
241
+ end
242
+
243
+ module EventDSL
244
+ def socks5_bytestream
245
+ Cancer::XEP_0065::EventMatcher.new
246
+ end
247
+ end
248
+
249
+ module TCPSocketHelper
250
+ attr_accessor :peer, :stream_id, :streamhost
251
+ end
252
+
253
+ module SOCKS_5
254
+ class Error < IOError ; end
255
+ def auth
256
+ write("\x05\x01\x00")
257
+ buf = read(2)
258
+ if buf.nil? or buf != "\x05\x00"
259
+ close
260
+ raise SOCKS_5::Error.new("Invalid SOCKS5 authentication: #{buf.inspect}")
261
+ end
262
+
263
+ self
264
+ end
265
+
266
+ def connect_domain(domain, port)
267
+ buf_out = "\x05\x01\x00\x03#{domain.size.chr}#{domain}#{[port].pack("n")}"
268
+ write(buf_out)
269
+ buf = read(4)
270
+ if buf.nil? or buf[0..1] != "\005\000"
271
+ close
272
+ raise SOCKS_5::Error.new("Invalid SOCKS5 connect: #{buf_out.inspect} => #{buf.inspect}")
273
+ end
274
+
275
+ case buf.respond_to?(:bytes) ? buf.bytes.to_a[3] : buf[3]
276
+ when 1 then read(6) # IPv4 addr
277
+ when 3 then read(3 + domain.size) # Domain
278
+ when 4 then read(18) # IPv6 addr
279
+ else
280
+ close
281
+ raise SOCKS_5::Error.new("Invalid SOCKS5 address type #{buf[3].to_s}")
282
+ end
283
+
284
+ self
285
+ end
286
+ end
287
+
288
+ class StreamMethod < Cancer::XEP_0095::StreamMethod
289
+ def handle(to, id, &proc)
290
+ @stream.open_socks5_bytestream(to, id, &proc)
291
+ end
292
+ end
293
+
294
+ class SOCKS5Controller < Cancer::Controller
295
+ on { |e| e.socks5_bytestream }
296
+ def catch_socks5(e)
297
+ controller = self.controllers_by_name['Cancer::XEP_0095::Controller']
298
+ si_event = controller.pending_streams[e.options[:sid].to_s]
299
+ if si_event
300
+ si_event.caught_stream!(e)
301
+ end
302
+ end
303
+ end
304
+
305
+ end
306
+ end