metasploit-framework/lib/ole/file_system.rb

171 lines
4.7 KiB
Ruby
Raw Normal View History

=begin
full file_system module
will be available and recommended usage, allowing Ole::Storage, Dir, and Zip::ZipFile to be
used pretty exchangably down the track. should be possible to write a recursive copy using
the plain api, such that you can copy dirs/files agnostically between any of ole docs, dirs,
and zip files.
i think its okay to have an api like this on top, but there are certain things that ole
does that aren't captured.
ole::storage can have multiple files with the same name, for example, or with / in the
name, and other things that are probably invalid anyway.
i think this should remain an addon, built on top of my core api.
but still the ideas can be reflected in the core, ie, changing the read/write semantics.
once the core changes are complete, this will be a pretty straight forward file to complete.
=end
module Ole
class Storage
def file
@file ||= FileParent.new self
end
def dir
@dir ||= DirParent.new self
end
def dirent_from_path path_str
path = path_str.sub(/^\/*/, '').sub(/\/*$/, '')
dirent = @root
return dirent if path.empty?
path = path.split /\/+/
until path.empty?
raise "invalid path #{path_str.inspect}" if dirent.file?
if tmp = dirent[path.shift]
dirent = tmp
else
# allow write etc later.
raise "invalid path #{path_str.inspect}"
end
end
dirent
end
class FileParent
def initialize ole
@ole = ole
end
def open path_str, mode='r'
dirent = @ole.dirent_from_path path_str
# like Errno::EISDIR
raise "#{path_str.inspect} is a directory" unless dirent.file?
io = dirent.io
if block_given?
yield io
else
io
end
end
alias new :open
def read path
open(path) { |f| f.read }
end
# crappy copy from Dir.
def unlink path
dirent = @ole.dirent_from_path path
# EPERM
raise "operation not permitted #{path.inspect}" unless dirent.file?
# i think we should free all of our blocks. i think the best way to do that would be
# like:
# open(path) { |f| f.truncate 0 }. which should free all our blocks from the
# allocation table. then if we remove ourself from our parent, we won't be part of
# the bat at save time.
# i think if you run repack, all free blocks should get zeroed.
parent = @ole.dirent_from_path(('/' + path).sub(/\/[^\/]+$/, ''))
parent.children.delete dirent
1 # hmmm. as per ::File ?
end
end
class DirParent
def initialize ole
@ole = ole
end
def open path_str
dirent = @ole.dirent_from_path path_str
# like Errno::ENOTDIR
raise "#{path_str.inspect} is not a directory" unless dirent.dir?
dir = Dir.new dirent, path_str
if block_given?
yield dir
else
dir
end
end
# certain Dir class methods proxy in this fashion:
def entries path
open(path) { |dir| dir.entries }
end
# there are some other important ones, like:
# chroot (!), mkdir, chdir, rmdir, glob etc etc. for now, i think
# mkdir, and rmdir are the main ones we'd need to support
def rmdir path
dirent = @ole.dirent_from_path path
p dirent
# repeating myself
raise "#{path.inspect} is not a directory" unless dirent.dir?
# ENOTEMPTY:
raise "directory not empty #{path.inspect}" unless dirent.children.empty?
# now delete it, how to do that? the canonical representation that is
# maintained is the root tree, and the children array. we must remove it
# from the children array.
# we need the parent then. this sucks but anyway:
parent = @ole.dirent_from_path path.sub(/\/[^\/]+$/, '') || '/'
# note that the way this currently works, on save and repack time this will get
# reflected. to work properly, ie to make a difference now it would have to re-write
# the dirent. i think that Ole::Storage#close will handle that. and maybe include a
# #repack.
parent.children.delete dirent
0 # hmmm. as per ::Dir ?
end
class Dir
include Enumerable
attr_reader :dirent, :path, :entries, :pos
def initialize dirent, path
@dirent, @path = dirent, path
@pos = 0
# FIXME: hack, and probably not really desired
@entries = %w[. ..] + @dirent.children.map(&:name)
end
def each(&block)
@entries.each(&block)
end
def close
end
def read
@entries[@pos]
ensure
@pos += 1 if @pos < @entries.length
end
def pos= pos
@pos = [[0, pos].max, @entries.length].min
end
def rewind
@pos = 0
end
alias tell :pos
alias seek :pos=
end
end
end
end