module Blather class FileTransfer # SOCKS5 Bytestreams Transfer helper # Takes care of accepting, declining and offering file transfers through the stream class S5b # Set this to false if you don't want to fallback to In-Band Bytestreams attr_accessor :allow_ibb_fallback # Set this to true if the buddies of your bot will be in the same local network # # Usually IM clients advertise all network addresses which they can determine. # Skipping the local ones can save time if your bot is not in the same local network as it's buddies attr_accessor :allow_private_ips def initialize(stream, iq) @stream = stream @iq = iq @allow_ibb_fallback = true @allow_private_ips = false end # Accept an incoming file-transfer # # @param [module] handler the handler for incoming data, see Blather::FileTransfer::SimpleFileReceiver for an example # @param [Array] params the params to be passed into the handler def accept(handler, *params) @streamhosts = @iq.streamhosts @streamhosts.delete_if {|s| begin IPAddr.new(s.host).private? rescue false end } unless @allow_private_ips @socket_address = Digest::SHA1.hexdigest("#{@iq.sid}#{@iq.from}#{@iq.to}") @handler = handler @params = params connect_next_streamhost @stream.clear_handlers :s5b_open, :from => @iq.from end # Decline an incoming file-transfer def decline @stream.clear_handlers :s5b_open, :from => @iq.from @stream.write StanzaError.new(@iq, 'not-acceptable', :auth).to_node end # Offer a file to somebody, not implemented yet def offer # TODO: implement end private def connect_next_streamhost if streamhost = @streamhosts.shift connect(streamhost) else if @allow_ibb_fallback @stream.register_handler :ibb_open, :from => @iq.from, :sid => @iq.sid do |iq| transfer = Blather::FileTransfer::Ibb.new(@stream, iq) transfer.accept(@handler, *@params) true end end @stream.write StanzaError.new(@iq, 'item-not-found', :cancel).to_node end end def connect(streamhost) begin socket = EM.connect streamhost.host, streamhost.port, SocketConnection, @socket_address, 0, @handler, *@params socket.callback do answer = @iq.reply answer.streamhosts = nil answer.streamhost_used = streamhost.jid @stream.write answer end socket.errback do connect_next_streamhost end rescue EventMachine::ConnectionError => e connect_next_streamhost end end # @private class SocketConnection < EM::P::Socks5 include EM::Deferrable def initialize(host, port, handler, *params) super(host, port) @@handler = handler @params = params end def post_init self.succeed class << self include @@handler end send(:initialize, *@params) post_init end def unbind self.fail if @socks_state != :connected end end end end end