class RubySMB::SMB2::File
Represents a file on the Remote server that we can perform various I/O operations on.
Constants
- MAX_PACKET_SIZE
The maximum number of byte we want to read or write in a single packet.
Attributes
The {FileAttributes} for the file @!attribute [rw] attributes
@return [RubySMB::Fscc::FileAttributes]
The {Smb2FileId} for the file @!attribute [rw] guid
@return [RubySMB::Field::Smb2FileId]
The last access date/time for the file @!attribute [rw] last_access
@return [DateTime]
The last change date/time for the file @!attribute [rw] last_change
@return [DateTime]
The last write date/time for the file @!attribute [rw] last_write
@return [DateTime]
The name of the file @!attribute [rw] name
@return [String]
The actual size, in bytes, of the file @!attribute [rw] size
@return [Integer]
The size in bytes that the file occupies on disk @!attribute [rw] size_on_disk
@return [Integer]
The {RubySMB::SMB2::Tree} that this file belong to @!attribute [rw] tree
@return [RubySMB::SMB2::Tree]
Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x) @!attribute [rw] tree_connect_encrypt_data
@return [Boolean]
Public Class Methods
# File lib/ruby_smb/smb2/file.rb, line 60 def initialize(tree:, response:, name:, encrypt: false) raise ArgumentError, 'No Tree Provided' if tree.nil? raise ArgumentError, 'No Response Provided' if response.nil? @tree = tree @name = name @attributes = response.file_attributes @guid = response.file_id @last_access = response.last_access.to_datetime @last_change = response.last_change.to_datetime @last_write = response.last_write.to_datetime @size = response.end_of_file @size_on_disk = response.allocation_size @tree_connect_encrypt_data = encrypt end
Public Instance Methods
Appends the supplied data to the end of the file.
@param data [String] the data to write to the file @return [WindowsError::ErrorCode] the NTStatus code returned from the operation
# File lib/ruby_smb/smb2/file.rb, line 81 def append(data:'') write(data: data, offset: size) end
Closes the handle to the remote file.
@return [WindowsError::ErrorCode] the NTStatus code returned by the operation @raise [RubySMB::Error::InvalidPacket] if the response is not a CloseResponse packet @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
# File lib/ruby_smb/smb2/file.rb, line 90 def close close_request = set_header_fields(RubySMB::SMB2::Packet::CloseRequest.new) raw_response = tree.client.send_recv(close_request, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::CloseResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::CloseResponse::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end response.status_code end
Delete a file on close
@return [WindowsError::ErrorCode] the NTStatus Response code @raise [RubySMB::Error::InvalidPacket] if the response is not a SetInfoResponse packet
# File lib/ruby_smb/smb2/file.rb, line 202 def delete raw_response = tree.client.send_recv(delete_packet, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::SetInfoResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::SetInfoResponse::COMMAND, packet: response ) end response.smb2_header.nt_status.to_nt_status end
Crafts the SetInfoRequest packet to be sent for delete operations.
@return [RubySMB::SMB2::Packet::SetInfoRequest] the set info packet
# File lib/ruby_smb/smb2/file.rb, line 218 def delete_packet delete_request = set_header_fields(RubySMB::SMB2::Packet::SetInfoRequest.new) delete_request.file_info_class = RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION delete_request.buffer.delete_pending = 1 delete_request end
Read from the file, a specific number of bytes from a specific offset. If no parameters are given it will read the entire file.
@param bytes [Integer] the number of bytes to read @param offset [Integer] the byte offset in the file to start reading from @return [String] the data read from the file @raise [RubySMB::Error::InvalidPacket] if the response is not a ReadResponse packet @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
# File lib/ruby_smb/smb2/file.rb, line 116 def read(bytes: size, offset: 0) max_read = tree.client.server_max_read_size max_read = 65536 unless tree.client.server_supports_multi_credit atomic_read_size = [bytes, max_read].min credit_charge = 0 if tree.client.server_supports_multi_credit credit_charge = (atomic_read_size - 1) / 65536 + 1 end read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge) raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::ReadResponse::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end data = response.buffer.to_binary_s remaining_bytes = bytes - atomic_read_size while remaining_bytes > 0 offset += atomic_read_size atomic_read_size = remaining_bytes if remaining_bytes < max_read read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge) raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::ReadResponse::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end data << response.buffer.to_binary_s remaining_bytes -= atomic_read_size end data end
Crafts the ReadRequest packet to be sent for read operations.
@param bytes [Integer] the number of bytes to read @param offset [Integer] the byte offset in the file to start reading from @param credit_charge [Integer] the number of credits that this request consumes @return [RubySMB::SMB2::Packet::ReadRequest] the data read from the file
# File lib/ruby_smb/smb2/file.rb, line 173 def read_packet(read_length: 0, offset: 0, credit_charge: 1) read_request = set_header_fields(RubySMB::SMB2::Packet::ReadRequest.new) read_request.read_length = read_length read_request.offset = offset read_request.smb2_header.credit_charge = credit_charge read_request end
Rename a file
@param new_file_name [String] the new name @return [WindowsError::ErrorCode] the NTStatus Response code @raise [RubySMB::Error::InvalidPacket] if the response is not a SetInfoResponse packet
# File lib/ruby_smb/smb2/file.rb, line 308 def rename(new_file_name) raw_response = tree.client.send_recv(rename_packet(new_file_name), encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::SetInfoResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::SetInfoResponse::COMMAND, packet: response ) end response.smb2_header.nt_status.to_nt_status end
Crafts the SetInfoRequest packet to be sent for rename operations.
@param new_file_name [String] the new name @return [RubySMB::SMB2::Packet::SetInfoRequest] the set info packet
# File lib/ruby_smb/smb2/file.rb, line 325 def rename_packet(new_file_name) rename_request = set_header_fields(RubySMB::SMB2::Packet::SetInfoRequest.new) rename_request.file_info_class = RubySMB::Fscc::FileInformation::FILE_RENAME_INFORMATION rename_request.buffer.file_name = new_file_name.encode('utf-16le') rename_request end
# File lib/ruby_smb/smb2/file.rb, line 181 def send_recv_read(read_length: 0, offset: 0) read_request = read_packet(read_length: read_length, offset: offset) raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::ReadResponse::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end response.buffer.to_binary_s end
# File lib/ruby_smb/smb2/file.rb, line 286 def send_recv_write(data:'', offset: 0) pkt = write_packet(data: data, offset: offset) raw_response = tree.client.send_recv(pkt, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::WriteResponse::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end response.write_count end
Sets the header fields that we have to set on every packet we send for File
operations. @param request [RubySMB::GenericPacket] the request packet to set fields on @return [RubySMB::GenericPacket] the rmodified request packet
# File lib/ruby_smb/smb2/file.rb, line 229 def set_header_fields(request) request = tree.set_header_fields(request) request.file_id = guid request end
Write the supplied data to the file at the given offset.
@param data [String] the data to write to the file @param offset [Integer] the offset in the file to start writing from @return [WindowsError::ErrorCode] the NTStatus code returned from the operation @raise [RubySMB::Error::InvalidPacket] if the response is not a WriteResponse packet
# File lib/ruby_smb/smb2/file.rb, line 241 def write(data:'', offset: 0) max_write = tree.client.server_max_write_size max_write = 65536 unless tree.client.server_supports_multi_credit buffer = data.dup bytes = data.length atomic_write_size = [bytes, max_write].min credit_charge = 0 if tree.client.server_supports_multi_credit credit_charge = (atomic_write_size - 1) / 65536 + 1 end while buffer.length > 0 do write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset, credit_charge: credit_charge) raw_response = tree.client.send_recv(write_request, encrypt: @tree_connect_encrypt_data) response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID, expected_cmd: RubySMB::SMB2::Packet::WriteResponse::COMMAND, packet: response ) end status = response.smb2_header.nt_status.to_nt_status offset += atomic_write_size return status unless status == WindowsError::NTStatus::STATUS_SUCCESS end status end
Creates the Request packet for the write
command
@param data [String] the data to write to the file @param offset [Integer] the offset in the file to start writing from @param credit_charge [Integer] the number of credits that this request consumes @return []RubySMB::SMB2::Packet::WriteRequest] the request packet
# File lib/ruby_smb/smb2/file.rb, line 278 def write_packet(data:'', offset: 0, credit_charge: 1) write_request = set_header_fields(RubySMB::SMB2::Packet::WriteRequest.new) write_request.write_offset = offset write_request.buffer = data write_request.smb2_header.credit_charge = credit_charge write_request end