This is the driver object for the SFTP protocol. It manages the SSH channel used to communicate with the server, as well as the negotiation of the protocol. The operations themselves are specific to the protocol version in use, and are handled by protocol-version-specific dispatcher objects.
- close
- do_confirm
- do_data
- do_success
- do_version
- method_missing
- new
- next_request_id
- on_open
- respond_to?
- send_data
[R] | state | The current state of the driver. This will be one of unconfirmed, init, version, open, or closed. |
Create a new SFTP protocol driver object on the given SSH connection. buffers is a reference to a buffer factory, version is the highest supported SFTP protocol version, dispatchers is a Proc object that returns a dispatcher instance for a specific protocol version, and log is a logger instance.
The new protocol driver will be in an unconfirmed state, initially. When the server validates the requested channel, the driver goes to the init state, and requests the SFTP subsystem. When the subsystem has been accepted, the driver sends its supported protocol version to the server, and goes to the version state. Lastly, when the server responds with its supported protocol version and the version to use has been successfully negotiated, the driver will go to the open state, after which SFTP operations may be successfully performed on the driver.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 48 48: def initialize( connection, buffers, version, dispatchers, log ) 49: @buffers = buffers 50: @version = version 51: @dispatchers = dispatchers 52: @log = log 53: 54: @next_request_id = 0 55: @next_request_mutex = Mutex.new 56: @parsed_data = nil 57: @on_open = nil 58: 59: @state = :unconfirmed 60: 61: @log.debug "opening channel for sftp" if @log.debug? 62: @channel = connection.open_channel( "session", &method( :do_confirm ) ) 63: end
Closes the underlying SSH channel that the SFTP session uses to communicate with the server. This moves the driver to the closed state. If the driver is already closed, this does nothing.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 68 68: def close 69: if @state != :closed 70: @log.debug "closing sftp channel" if @log.debug? 71: @channel.close 72: @state = :closed 73: end 74: end
The callback used internally to indicate that the requested channel has been confirmed. This will request the SFTP subsystem, register some request callbacks, and move the driver’s state to init. This may only be called when the driver’s state is unconfirmed.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 97 97: def do_confirm( channel ) 98: assert_state :unconfirmed 99: @log.debug "requesting sftp subsystem" if @log.debug? 100: 101: channel.subsystem "sftp" 102: channel.on_success &method( :do_success ) 103: channel.on_data &method( :do_data ) 104: 105: @state = :init 106: end
This is called internally when a data packet is received from the server. All SFTP packets are transfered as SSH data packets, so this parses the data packet to determine the SFTP packet type, and then sends the contents on to the active dispatcher for further processing. This routine correctly handles SFTP packets that span multiple SSH data packets.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 160 160: def do_data( channel, data ) 161: if @parsed_data 162: @parsed_data[:content].append data 163: return if @parsed_data[:length] > @parsed_data[:content].length 164: 165: type = @parsed_data[:type] 166: content = @parsed_data[:content] 167: @parsed_data = nil 168: else 169: reader = @buffers.reader( data ) 170: length = reader.read_long-1 171: type = reader.read_byte 172: content = reader.remainder_as_buffer 173: 174: if length > content.length 175: @parsed_data = { :length => length, 176: :type => type, 177: :content => content } 178: return 179: end 180: end 181: 182: if type == FXP_VERSION 183: do_version content 184: else 185: assert_state :open 186: @dispatcher.dispatch channel, type, content 187: end 188: end
The callback used internally to indicate that the SFTP subsystem was successfully requested. This may only be called when the driver’s state is init. It sends an INIT packet containing the highest supported SFTP protocol version to the server, and moves the driver’s state to version.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 113 113: def do_success( channel ) 114: assert_state :init 115: @log.debug "initializing sftp subsystem" if @log.debug? 116: 117: packet = @buffers.writer 118: packet.write_long @version 119: send_data FXP_INIT, packet 120: 121: @state = :version 122: end
This is used internally to indicate that a VERSION packet was received from the server. This may only be called when the driver’s state is version. It determines the highest possible protocol version supported by both the client and the server, selects the dispatcher that handles that protocol version, moves the state to open, and then invokes the on_open callback (if one was registered).
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 130 130: def do_version( content ) 131: assert_state :version 132: @log.debug "negotiating sftp protocol version" if @log.debug? 133: @log.debug "my sftp version is #{@version}" if @log.debug? 134: 135: server_version = content.read_long 136: @log.debug "server reports sftp version #{server_version}" if @log.debug? 137: 138: negotiated_version = [ @version, server_version ].min 139: @log.info "negotiated version is #{negotiated_version}" if @log.info? 140: 141: extensions = Hash.new 142: until content.eof? 143: ext_name = content.read_string 144: ext_data = content.read_string 145: extensions[ ext_name ] = ext_data 146: end 147: 148: @dispatcher = @dispatchers[ negotiated_version, extensions ] 149: 150: @state = :open 151: 152: @on_open.call( self ) if @on_open 153: end
Delegates missing methods to the current dispatcher (if the state is open). This allows clients to register callbacks for the supported operations of the negotiated protocol version.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 193 193: def method_missing( sym, *args, &block ) 194: if @state == :open && @dispatcher.respond_to?( sym ) 195: assert_state :open 196: @dispatcher.__send__( sym, *args, &block ) 197: else 198: super 199: end 200: end
Returns the next available request id in a thread-safe manner. The request-id is used to identify packets associated with request sequences.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 78 78: def next_request_id 79: @next_request_mutex.synchronize do 80: request_id = @next_request_id 81: @next_request_id += 1 82: return request_id 83: end 84: end
Specify the callback to invoke when the session has been successfully opened (i.e., once the driver’s state has moved to open). The callback should accept a single parameter—the driver itself.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 89 89: def on_open( &block ) 90: @on_open = block 91: end
Returns true if the driver responds to the given message, or if the state is open and the active dispatcher responds to the given message.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 205 205: def respond_to?( sym ) 206: super || @state == :open && @dispatcher.respond_to?( sym ) 207: end
A convenience method for sending an SFTP packet of the given type, with the given payload. This repackages the data as an SSH data packet and sends it across the channel.
[ show source ]
# File lib/net/sftp/protocol/driver.rb, line 212 212: def send_data( type, data ) 213: data = data.to_s 214: 215: msg = @buffers.writer 216: msg.write_long data.length + 1 217: msg.write_byte type 218: msg.write data 219: 220: @channel.send_data msg 221: end