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] | channel | The underlying SSH channel supporting this SFTP connection. |
[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 51 51: def initialize( connection, buffers, version, dispatchers, log ) 52: @buffers = buffers 53: @version = version 54: @dispatchers = dispatchers 55: @log = log 56: 57: @next_request_id = 0 58: @next_request_mutex = Mutex.new 59: @parsed_data = nil 60: @on_open = nil 61: 62: @state = :unconfirmed 63: 64: @log.debug "opening channel for sftp" if @log.debug? 65: @channel = connection.open_channel( "session", &method( :do_confirm ) ) 66: 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 71 71: def close 72: if @state != :closed 73: @log.debug "closing sftp channel" if @log.debug? 74: @channel.close 75: @state = :closed 76: end 77: 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 100 100: def do_confirm( channel ) 101: assert_state :unconfirmed 102: @log.debug "requesting sftp subsystem" if @log.debug? 103: 104: channel.subsystem "sftp" 105: channel.on_success &method( :do_success ) 106: channel.on_data &method( :do_data ) 107: 108: @state = :init 109: 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 163 163: def do_data( channel, data ) 164: if @parsed_data 165: @parsed_data[:content].append data 166: return if @parsed_data[:length] > @parsed_data[:content].length 167: 168: type = @parsed_data[:type] 169: content = @parsed_data[:content] 170: @parsed_data = nil 171: else 172: reader = @buffers.reader( data ) 173: length = reader.read_long-1 174: type = reader.read_byte 175: content = reader.remainder_as_buffer 176: 177: if length > content.length 178: @parsed_data = { :length => length, 179: :type => type, 180: :content => content } 181: return 182: end 183: end 184: 185: if type == FXP_VERSION 186: do_version content 187: else 188: assert_state :open 189: @dispatcher.dispatch channel, type, content 190: end 191: 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 116 116: def do_success( channel ) 117: assert_state :init 118: @log.debug "initializing sftp subsystem" if @log.debug? 119: 120: packet = @buffers.writer 121: packet.write_long @version 122: send_data FXP_INIT, packet 123: 124: @state = :version 125: 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 133 133: def do_version( content ) 134: assert_state :version 135: @log.debug "negotiating sftp protocol version" if @log.debug? 136: @log.debug "my sftp version is #{@version}" if @log.debug? 137: 138: server_version = content.read_long 139: @log.debug "server reports sftp version #{server_version}" if @log.debug? 140: 141: negotiated_version = [ @version, server_version ].min 142: @log.info "negotiated version is #{negotiated_version}" if @log.info? 143: 144: extensions = Hash.new 145: until content.eof? 146: ext_name = content.read_string 147: ext_data = content.read_string 148: extensions[ ext_name ] = ext_data 149: end 150: 151: @dispatcher = @dispatchers[ negotiated_version, extensions ] 152: 153: @state = :open 154: 155: @on_open.call( self ) if @on_open 156: 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 196 196: def method_missing( sym, *args, &block ) 197: if @state == :open && @dispatcher.respond_to?( sym ) 198: assert_state :open 199: @dispatcher.__send__( sym, *args, &block ) 200: else 201: super 202: end 203: 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 81 81: def next_request_id 82: @next_request_mutex.synchronize do 83: request_id = @next_request_id 84: @next_request_id += 1 85: return request_id 86: end 87: 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 92 92: def on_open( &block ) 93: @on_open = block 94: 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 208 208: def respond_to?( sym ) 209: super || @state == :open && @dispatcher.respond_to?( sym ) 210: 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 215 215: def send_data( type, data ) 216: data = data.to_s 217: 218: msg = @buffers.writer 219: msg.write_long data.length + 1 220: msg.write_byte type 221: msg.write data 222: 223: @channel.send_data msg 224: end