Class: RubySMB::SMB2::File

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_smb/smb2/file.rb

Overview

Represents a file on the Remote server that we can perform various I/O operations on.

Direct Known Subclasses

Pipe

Constant Summary collapse

MAX_PACKET_SIZE =

The maximum number of byte we want to read or write in a single packet.

32_768

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree:, response:, name:) ⇒ File

Returns a new instance of File.

Raises:

  • (ArgumentError)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruby_smb/smb2/file.rb', line 55

def initialize(tree:, response:, name:)
  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
end

Instance Attribute Details

#attributesRubySMB::Fscc::FileAttributes



13
14
15
# File 'lib/ruby_smb/smb2/file.rb', line 13

def attributes
  @attributes
end

#guidRubySMB::Field::Smb2FileId

Returns:

  • (RubySMB::Field::Smb2FileId)


18
19
20
# File 'lib/ruby_smb/smb2/file.rb', line 18

def guid
  @guid
end

#last_accessDateTime

Returns:

  • (DateTime)


23
24
25
# File 'lib/ruby_smb/smb2/file.rb', line 23

def last_access
  @last_access
end

#last_changeDateTime

Returns:

  • (DateTime)


28
29
30
# File 'lib/ruby_smb/smb2/file.rb', line 28

def last_change
  @last_change
end

#last_writeDateTime

Returns:

  • (DateTime)


33
34
35
# File 'lib/ruby_smb/smb2/file.rb', line 33

def last_write
  @last_write
end

#nameString

Returns:

  • (String)


38
39
40
# File 'lib/ruby_smb/smb2/file.rb', line 38

def name
  @name
end

#sizeInteger

Returns:

  • (Integer)


43
44
45
# File 'lib/ruby_smb/smb2/file.rb', line 43

def size
  @size
end

#size_on_diskInteger

Returns:

  • (Integer)


48
49
50
# File 'lib/ruby_smb/smb2/file.rb', line 48

def size_on_disk
  @size_on_disk
end

#treeRubySMB::SMB2::Tree

Returns:



53
54
55
# File 'lib/ruby_smb/smb2/file.rb', line 53

def tree
  @tree
end

Instance Method Details

#append(data: '') ⇒ WindowsError::ErrorCode

Appends the supplied data to the end of the file.

Parameters:

  • data (String) (defaults to: '')

    the data to write to the file

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned from the operation



75
76
77
# File 'lib/ruby_smb/smb2/file.rb', line 75

def append(data:'')
  write(data: data, offset: size)
end

#closeWindowsError::ErrorCode

Closes the handle to the remote file.

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned by the operation



82
83
84
85
86
87
# File 'lib/ruby_smb/smb2/file.rb', line 82

def close
  close_request = set_header_fields(RubySMB::SMB2::Packet::CloseRequest.new)
  raw_response  = tree.client.send_recv(close_request)
  response = RubySMB::SMB2::Packet::CloseResponse.read(raw_response)
  response.smb2_header.nt_status.to_nt_status
end

#deleteWindowsError::ErrorCode

Delete a file on close

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus Response code



154
155
156
157
158
# File 'lib/ruby_smb/smb2/file.rb', line 154

def delete
  raw_response = tree.client.send_recv(delete_packet)
  response = RubySMB::SMB2::Packet::SetInfoResponse.read(raw_response)
  response.smb2_header.nt_status.to_nt_status
end

#delete_packetRubySMB::SMB2::Packet::SetInfoRequest

Crafts the SetInfoRequest packet to be sent for delete operations.

Returns:



163
164
165
166
167
168
# File 'lib/ruby_smb/smb2/file.rb', line 163

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(bytes: size, offset: 0) ⇒ String

Read from the file, a specific number of bytes from a specific offset. If no parameters are given it will read the entire file.

Parameters:

  • bytes (Integer) (defaults to: size)

    the number of bytes to read

  • offset (Integer) (defaults to: 0)

    the byte offset in the file to start reading from

Returns:

  • (String)

    the data read from the file



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ruby_smb/smb2/file.rb', line 96

def read(bytes: size, offset: 0)
  atomic_read_size = if bytes > tree.client.server_max_read_size
                       tree.client.server_max_read_size
                     else
                       bytes
                     end

  read_request = read_packet(read_length: atomic_read_size, offset: offset)
  raw_response = tree.client.send_recv(read_request)
  response     = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)

  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 < tree.client.server_max_read_size

    read_request = read_packet(read_length: atomic_read_size, offset: offset)
    raw_response = tree.client.send_recv(read_request)
    response     = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)

    data << response.buffer.to_binary_s
    remaining_bytes -= atomic_read_size
  end
  data
end

#read_packet(read_length: 0, offset: 0) ⇒ RubySMB::SMB2::Packet::ReadRequest

Crafts the ReadRequest packet to be sent for read operations.

Parameters:

  • bytes (Integer)

    the number of bytes to read

  • offset (Integer) (defaults to: 0)

    the byte offset in the file to start reading from

Returns:



130
131
132
133
134
135
# File 'lib/ruby_smb/smb2/file.rb', line 130

def read_packet(read_length: 0, offset: 0)
  read_request = set_header_fields(RubySMB::SMB2::Packet::ReadRequest.new)
  read_request.read_length  = read_length
  read_request.offset       = offset
  read_request
end

#rename(new_file_name) ⇒ WindowsError::ErrorCode

Rename a file

Parameters:

  • new_file_name (String)

    the new name

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus Response code



237
238
239
240
241
# File 'lib/ruby_smb/smb2/file.rb', line 237

def rename(new_file_name)
  raw_response = tree.client.send_recv(rename_packet(new_file_name))
  response = RubySMB::SMB2::Packet::SetInfoResponse.read(raw_response)
  response.smb2_header.nt_status.to_nt_status
end

#rename_packet(new_file_name) ⇒ RubySMB::SMB2::Packet::SetInfoRequest

Crafts the SetInfoRequest packet to be sent for rename operations.

Parameters:

  • new_file_name (String)

    the new name

Returns:



247
248
249
250
251
252
# File 'lib/ruby_smb/smb2/file.rb', line 247

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

#send_recv_read(read_length: 0, offset: 0) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ruby_smb/smb2/file.rb', line 137

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)
  response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
  if response.status_code == WindowsError::NTStatus::STATUS_PENDING
    sleep 1
    raw_response = tree.client.dispatcher.recv_packet
    response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
  elsif response.status_code != WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
  end
  response.buffer.to_binary_s
end

#send_recv_write(data: '', offset: 0) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/ruby_smb/smb2/file.rb', line 219

def send_recv_write(data:'', offset: 0)
  pkt = write_packet(data: data, offset: offset)
  raw_response = tree.client.send_recv(pkt)
  response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
  if response.status_code == WindowsError::NTStatus::STATUS_PENDING
    sleep 1
    raw_response = tree.client.dispatcher.recv_packet
    response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
  elsif response.status_code != WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
  end
  response.write_count
end

#set_header_fields(request) ⇒ RubySMB::GenericPacket

Sets the header fields that we have to set on every packet we send for File operations.

Parameters:

Returns:



174
175
176
177
178
# File 'lib/ruby_smb/smb2/file.rb', line 174

def set_header_fields(request)
  request         = tree.set_header_fields(request)
  request.file_id = guid
  request
end

#write(data: '', offset: 0) ⇒ WindowsError::ErrorCode

Write the supplied data to the file at the given offset.

Parameters:

  • data (String) (defaults to: '')

    the data to write to the file

  • offset (Integer) (defaults to: 0)

    the offset in the file to start writing from

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned from the operation



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/ruby_smb/smb2/file.rb', line 185

def write(data:'', offset: 0)
  buffer            = data.dup
  bytes             = data.length
  atomic_write_size = if bytes > tree.client.server_max_write_size
                        tree.client.server_max_write_size
                      else
                       bytes
                      end

  while buffer.length > 0 do
    write_request = write_packet(data: buffer.slice!(0,atomic_write_size), offset: offset)
    raw_response  = tree.client.send_recv(write_request)
    response      = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
    status        = response.smb2_header.nt_status.to_nt_status

    offset+= atomic_write_size
    return status unless status == WindowsError::NTStatus::STATUS_SUCCESS
  end

  status
end

#write_packet(data: '', offset: 0) ⇒ Object

Creates the Request packet for the #write command

Parameters:

  • data (String) (defaults to: '')

    the data to write to the file

  • offset (Integer) (defaults to: 0)

    the offset in the file to start writing from

Returns:

  • []RubySMB::SMB2::Packet::WriteRequest] the request packet



212
213
214
215
216
217
# File 'lib/ruby_smb/smb2/file.rb', line 212

def write_packet(data:'', offset: 0)
  write_request               = set_header_fields(RubySMB::SMB2::Packet::WriteRequest.new)
  write_request.write_offset  = offset
  write_request.buffer        = data
  write_request
end