rubyzip/lib/zip/filesystem.rb

627 lines
16 KiB
Ruby
Raw Normal View History

require 'zip'
2002-07-27 04:58:51 +08:00
module Zip
2005-02-18 04:27:02 +08:00
# The ZipFileSystem API provides an API for accessing entries in
# a zip archive that is similar to ruby's builtin File and Dir
2005-02-18 04:27:02 +08:00
# classes.
#
# Requiring 'zip/filesystem' includes this module in Zip::File
# making the methods in this module available on Zip::File objects.
2005-02-18 04:27:02 +08:00
#
# Using this API the following example creates a new zip file
2005-02-18 04:27:02 +08:00
# <code>my.zip</code> containing a normal entry with the name
# <code>first.txt</code>, a directory entry named <code>mydir</code>
# and finally another normal entry named <code>second.txt</code>
#
# require 'zip/filesystem'
#
# Zip::File.open("my.zip", Zip::File::CREATE) {
2005-02-18 04:27:02 +08:00
# |zipfile|
# zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
# zipfile.dir.mkdir("mydir")
# zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
# }
#
# Reading is as easy as writing, as the following example shows. The
2005-02-18 04:27:02 +08:00
# example writes the contents of <code>first.txt</code> from zip archive
# <code>my.zip</code> to standard out.
#
# require 'zip/filesystem'
#
# Zip::File.open("my.zip") {
2005-02-18 04:27:02 +08:00
# |zipfile|
# puts zipfile.file.read("first.txt")
# }
module FileSystem
2005-02-18 04:27:02 +08:00
def initialize # :nodoc:
mappedZip = ZipFileNameMapper.new(self)
@zipFsDir = ZipFsDir.new(mappedZip)
@zipFsFile = ZipFsFile.new(mappedZip)
@zipFsDir.file = @zipFsFile
@zipFsFile.dir = @zipFsDir
end
2005-02-18 04:27:02 +08:00
# Returns a ZipFsDir which is much like ruby's builtin Dir (class)
# object, except it works on the Zip::File on which this method is
2005-02-18 04:27:02 +08:00
# invoked
def dir
@zipFsDir
end
2011-11-16 23:09:14 +08:00
2005-02-18 04:27:02 +08:00
# Returns a ZipFsFile which is much like ruby's builtin File (class)
# object, except it works on the Zip::File on which this method is
2005-02-18 04:27:02 +08:00
# invoked
def file
@zipFsFile
end
2005-02-18 04:27:02 +08:00
# Instances of this class are normally accessed via the accessor
# Zip::File::file. An instance of ZipFsFile behaves like ruby's
# builtin File (class) object, except it works on Zip::File entries.
2005-02-18 04:27:02 +08:00
#
# The individual methods are not documented due to their
# similarity with the methods in File
class ZipFsFile
attr_writer :dir
# protected :dir
class ZipFsStat
2012-02-01 17:47:17 +08:00
class << self
def delegate_to_fs_file(*methods)
methods.each do |method|
self.class_eval <<-end_eval, __FILE__, __LINE__ + 1
def #{method} # def file?
@zipFsFile.#{method}(@entryName) # @zipFsFile.file?(@entryName)
end # end
end_eval
end
end
end
def initialize(zipFsFile, entryName)
@zipFsFile = zipFsFile
@entryName = entryName
end
2011-11-16 23:09:14 +08:00
def kind_of?(t)
super || t == ::File::Stat
end
2011-11-16 23:09:14 +08:00
2012-02-01 17:47:17 +08:00
delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?,
2015-03-21 15:44:56 +08:00
:socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime,
:writable_real?, :executable?, :executable_real?, :sticky?, :owned?,
:grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime
2011-11-16 23:09:14 +08:00
def blocks; nil; end
2004-01-30 23:07:56 +08:00
def get_entry
@zipFsFile.__send__(:get_entry, @entryName)
end
private :get_entry
def gid
e = get_entry
if e.extra.member? "IUnix"
e.extra["IUnix"].gid || 0
else
0
end
end
2004-01-30 23:07:56 +08:00
def uid
e = get_entry
if e.extra.member? "IUnix"
e.extra["IUnix"].uid || 0
else
0
end
end
def ino; 0; end
def dev; 0; end
def rdev; 0; end
def rdev_major; 0; end
def rdev_minor; 0; end
def ftype
if file?
return "file"
elsif directory?
return "directory"
else
raise StandardError, "Unknown file type"
end
end
def nlink; 1; end
2011-11-16 23:09:14 +08:00
def blksize; nil; end
2004-01-30 23:07:56 +08:00
def mode
e = get_entry
if e.fstype == 3
2013-06-03 02:33:03 +08:00
e.external_file_attributes >> 16
2004-01-30 23:07:56 +08:00
else
33206 # 33206 is equivalent to -rw-rw-rw-
end
end
end
def initialize(mappedZip)
2011-11-16 23:04:28 +08:00
@mappedZip = mappedZip
end
2004-01-30 23:07:56 +08:00
def get_entry(fileName)
if ! exists?(fileName)
raise Errno::ENOENT, "No such file or directory - #{fileName}"
end
@mappedZip.find_entry(fileName)
end
private :get_entry
def unix_mode_cmp(fileName, mode)
begin
e = get_entry(fileName)
2013-06-03 02:33:03 +08:00
e.fstype == 3 && ((e.external_file_attributes >> 16) & mode ) != 0
2004-01-30 23:07:56 +08:00
rescue Errno::ENOENT
false
end
end
private :unix_mode_cmp
2011-11-16 23:09:14 +08:00
def exists?(fileName)
expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
end
2015-03-21 04:17:05 +08:00
alias_method :exist?, :exists?
2011-11-16 23:09:14 +08:00
# Permissions not implemented, so if the file exists it is accessible
2015-03-21 04:17:05 +08:00
alias_method :owned?, :exists?
alias_method :grpowned?, :exists?
2004-01-30 23:07:56 +08:00
def readable?(fileName)
unix_mode_cmp(fileName, 0444)
end
2015-03-21 04:17:05 +08:00
alias_method :readable_real?, :readable?
2004-01-30 23:07:56 +08:00
def writable?(fileName)
unix_mode_cmp(fileName, 0222)
end
2015-03-21 04:17:05 +08:00
alias_method :writable_real?, :writable?
2004-01-30 23:07:56 +08:00
def executable?(fileName)
unix_mode_cmp(fileName, 0111)
end
2015-03-21 04:17:05 +08:00
alias_method :executable_real?, :executable?
2004-01-30 23:07:56 +08:00
def setuid?(fileName)
2004-01-30 23:07:56 +08:00
unix_mode_cmp(fileName, 04000)
end
def setgid?(fileName)
2004-01-30 23:07:56 +08:00
unix_mode_cmp(fileName, 02000)
end
2011-11-16 23:09:14 +08:00
def sticky?(fileName)
2004-01-30 23:07:56 +08:00
unix_mode_cmp(fileName, 01000)
end
def umask(*args)
::File.umask(*args)
end
def truncate(_fileName, _len)
raise StandardError, "truncate not supported"
end
def directory?(fileName)
2011-11-16 23:09:14 +08:00
entry = @mappedZip.find_entry(fileName)
expand_path(fileName) == "/" || (entry != nil && entry.directory?)
end
2011-11-16 23:09:14 +08:00
def open(fileName, openMode = "r", permissionInt = 0644, &block)
openMode.gsub!("b", "") # ignore b option
case openMode
when "r"
@mappedZip.get_input_stream(fileName, &block)
when "w"
@mappedZip.get_output_stream(fileName, permissionInt, &block)
else
raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
end
end
2002-07-27 21:25:16 +08:00
def new(fileName, openMode = "r")
2011-11-16 23:09:14 +08:00
open(fileName, openMode)
2002-07-27 21:25:16 +08:00
end
2011-11-16 23:09:14 +08:00
def size(fileName)
2011-11-16 23:09:14 +08:00
@mappedZip.get_entry(fileName).size
end
2011-11-16 23:09:14 +08:00
2005-02-18 04:27:02 +08:00
# Returns nil for not found and nil for directories
def size?(fileName)
2011-11-16 23:09:14 +08:00
entry = @mappedZip.find_entry(fileName)
return (entry == nil || entry.directory?) ? nil : entry.size
end
2011-11-16 23:09:14 +08:00
2004-01-30 23:07:56 +08:00
def chown(ownerInt, groupInt, *filenames)
filenames.each { |fileName|
e = get_entry(fileName)
unless e.extra.member?("IUnix")
e.extra.create("IUnix")
end
e.extra["IUnix"].uid = ownerInt
e.extra["IUnix"].gid = groupInt
}
filenames.size
end
def chmod (modeInt, *filenames)
2004-01-30 23:07:56 +08:00
filenames.each { |fileName|
e = get_entry(fileName)
e.fstype = 3 # force convertion filesystem type to unix
e.unix_perms = modeInt
2013-06-03 02:33:03 +08:00
e.external_file_attributes = modeInt << 16
e.dirty = true
}
filenames.size
end
def zero?(fileName)
2011-11-16 23:04:28 +08:00
sz = size(fileName)
sz == nil || sz == 0
rescue Errno::ENOENT
2011-11-16 23:04:28 +08:00
false
end
2011-11-16 23:09:14 +08:00
def file?(fileName)
2011-11-16 23:04:28 +08:00
entry = @mappedZip.find_entry(fileName)
entry != nil && entry.file?
end
2011-11-16 23:09:14 +08:00
def dirname(fileName)
2011-11-16 23:04:28 +08:00
::File.dirname(fileName)
end
2011-11-16 23:09:14 +08:00
def basename(fileName)
2011-11-16 23:04:28 +08:00
::File.basename(fileName)
end
2011-11-16 23:09:14 +08:00
def split(fileName)
2011-11-16 23:04:28 +08:00
::File.split(fileName)
end
2011-11-16 23:09:14 +08:00
def join(*fragments)
2011-11-16 23:04:28 +08:00
::File.join(*fragments)
end
2011-11-16 23:09:14 +08:00
2004-01-30 23:07:56 +08:00
def utime(modifiedTime, *fileNames)
fileNames.each { |fileName|
get_entry(fileName).time = modifiedTime
}
end
def mtime(fileName)
2011-11-16 23:04:28 +08:00
@mappedZip.get_entry(fileName).mtime
end
2011-11-16 23:09:14 +08:00
def atime(fileName)
2004-01-30 23:07:56 +08:00
e = get_entry(fileName)
if e.extra.member? "UniversalTime"
e.extra["UniversalTime"].atime
2014-09-12 14:04:23 +08:00
elsif e.extra.member? "NTFS"
e.extra["NTFS"].atime
2004-01-30 23:07:56 +08:00
else
nil
end
end
2011-11-16 23:09:14 +08:00
def ctime(fileName)
2004-01-30 23:07:56 +08:00
e = get_entry(fileName)
if e.extra.member? "UniversalTime"
e.extra["UniversalTime"].ctime
2014-09-12 14:04:23 +08:00
elsif e.extra.member? "NTFS"
e.extra["NTFS"].ctime
2004-01-30 23:07:56 +08:00
else
nil
end
end
def pipe?(_filename)
2011-11-16 23:04:28 +08:00
false
end
2011-11-16 23:09:14 +08:00
def blockdev?(_filename)
2011-11-16 23:04:28 +08:00
false
end
2011-11-16 23:09:14 +08:00
def chardev?(_filename)
2011-11-16 23:04:28 +08:00
false
end
2011-11-16 23:09:14 +08:00
def symlink?(_fileName)
2011-11-16 23:04:28 +08:00
false
end
2011-11-16 23:09:14 +08:00
def socket?(_fileName)
2011-11-16 23:04:28 +08:00
false
end
2011-11-16 23:09:14 +08:00
def ftype(fileName)
2011-11-16 23:04:28 +08:00
@mappedZip.get_entry(fileName).directory? ? "directory" : "file"
end
2011-11-16 23:09:14 +08:00
def readlink(_fileName)
2011-11-16 23:04:28 +08:00
raise NotImplementedError, "The readlink() function is not implemented"
end
2011-11-16 23:09:14 +08:00
def symlink(_fileName, _symlinkName)
2011-11-16 23:04:28 +08:00
raise NotImplementedError, "The symlink() function is not implemented"
2002-07-27 06:23:32 +08:00
end
2002-07-27 06:25:36 +08:00
def link(_fileName, _symlinkName)
2011-11-16 23:04:28 +08:00
raise NotImplementedError, "The link() function is not implemented"
2002-07-27 06:25:36 +08:00
end
2002-07-27 06:30:04 +08:00
def pipe
2011-11-16 23:04:28 +08:00
raise NotImplementedError, "The pipe() function is not implemented"
2002-07-27 06:30:04 +08:00
end
def stat(fileName)
if ! exists?(fileName)
raise Errno::ENOENT, fileName
end
ZipFsStat.new(self, fileName)
end
2015-03-21 04:17:05 +08:00
alias_method :lstat, :stat
def readlines(fileName)
2011-11-16 23:04:28 +08:00
open(fileName) { |is| is.readlines }
end
def read(fileName)
@mappedZip.read(fileName)
end
def popen(*args, &aProc)
::File.popen(*args, &aProc)
end
def foreach(fileName, aSep = $/, &aProc)
2011-11-16 23:04:28 +08:00
open(fileName) { |is| is.each_line(aSep, &aProc) }
end
2002-09-12 06:10:34 +08:00
def delete(*args)
2011-11-16 23:04:28 +08:00
args.each {
|fileName|
if directory?(fileName)
raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
end
@mappedZip.remove(fileName)
}
2002-09-12 06:10:34 +08:00
end
def rename(fileToRename, newName)
@mappedZip.rename(fileToRename, newName) { true }
end
2015-03-21 04:17:05 +08:00
alias_method :unlink, :delete
2002-09-12 06:10:34 +08:00
def expand_path(aPath)
@mappedZip.expand_path(aPath)
end
2002-07-27 05:32:12 +08:00
end
2005-02-18 04:27:02 +08:00
# Instances of this class are normally accessed via the accessor
# ZipFile::dir. An instance of ZipFsDir behaves like ruby's
# builtin Dir (class) object, except it works on ZipFile entries.
#
# The individual methods are not documented due to their
# similarity with the methods in Dir
class ZipFsDir
2011-11-16 23:09:14 +08:00
def initialize(mappedZip)
@mappedZip = mappedZip
end
2011-11-16 23:09:14 +08:00
attr_writer :file
2003-08-21 20:32:01 +08:00
def new(aDirectoryName)
ZipFsDirIterator.new(entries(aDirectoryName))
end
def open(aDirectoryName)
dirIt = new(aDirectoryName)
if block_given?
begin
yield(dirIt)
return nil
ensure
dirIt.close
end
end
dirIt
end
def pwd; @mappedZip.pwd; end
2015-03-21 04:17:05 +08:00
alias_method :getwd, :pwd
2011-11-16 23:09:14 +08:00
def chdir(aDirectoryName)
unless @file.stat(aDirectoryName).directory?
raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
end
@mappedZip.pwd = @file.expand_path(aDirectoryName)
end
2011-11-16 23:09:14 +08:00
def entries(aDirectoryName)
entries = []
foreach(aDirectoryName) { |e| entries << e }
entries
end
def glob(*args,&block)
@mappedZip.glob(*args,&block)
end
def foreach(aDirectoryName)
unless @file.stat(aDirectoryName).directory?
raise Errno::ENOTDIR, aDirectoryName
end
2012-02-01 17:59:56 +08:00
path = @file.expand_path(aDirectoryName)
path << '/' unless path.end_with?('/')
2011-11-16 23:04:28 +08:00
path = Regexp.escape(path)
subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
@mappedZip.each {
|fileName|
match = subDirEntriesRegex.match(fileName)
yield(match[1]) unless match == nil
}
end
def delete(entryName)
unless @file.stat(entryName).directory?
raise Errno::EINVAL, "Invalid argument - #{entryName}"
end
@mappedZip.remove(entryName)
end
2015-03-21 04:17:05 +08:00
alias_method :rmdir, :delete
alias_method :unlink, :delete
2011-11-16 23:09:14 +08:00
2006-02-23 06:44:28 +08:00
def mkdir(entryName, permissionInt = 0755)
@mappedZip.mkdir(entryName, permissionInt)
end
2011-11-16 23:09:14 +08:00
def chroot(*_args)
2011-11-16 23:09:14 +08:00
raise NotImplementedError, "The chroot() function is not implemented"
2003-08-21 00:08:07 +08:00
end
end
2005-02-18 04:27:02 +08:00
class ZipFsDirIterator # :nodoc:all
2003-08-21 20:19:00 +08:00
include Enumerable
def initialize(arrayOfFileNames)
@fileNames = arrayOfFileNames
@index = 0
end
def close
@fileNames = nil
end
def each(&aProc)
raise IOError, "closed directory" if @fileNames == nil
@fileNames.each(&aProc)
end
def read
raise IOError, "closed directory" if @fileNames == nil
@fileNames[(@index+=1)-1]
end
def rewind
raise IOError, "closed directory" if @fileNames == nil
@index = 0
end
def seek(anIntegerPosition)
raise IOError, "closed directory" if @fileNames == nil
@index = anIntegerPosition
end
def tell
raise IOError, "closed directory" if @fileNames == nil
@index
end
end
# All access to Zip::File from ZipFsFile and ZipFsDir goes through a
# ZipFileNameMapper, which has one responsibility: ensure
2005-02-18 04:27:02 +08:00
class ZipFileNameMapper # :nodoc:all
include Enumerable
def initialize(zipFile)
@zipFile = zipFile
@pwd = "/"
end
2011-11-16 23:09:14 +08:00
attr_accessor :pwd
2011-11-16 23:09:14 +08:00
def find_entry(fileName)
@zipFile.find_entry(expand_to_entry(fileName))
end
2011-11-16 23:09:14 +08:00
def get_entry(fileName)
@zipFile.get_entry(expand_to_entry(fileName))
end
def get_input_stream(fileName, &aProc)
@zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
end
2011-11-16 23:09:14 +08:00
def get_output_stream(fileName, permissionInt = nil, &aProc)
@zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc)
end
def read(fileName)
@zipFile.read(expand_to_entry(fileName))
end
2011-11-16 23:09:14 +08:00
def remove(fileName)
@zipFile.remove(expand_to_entry(fileName))
end
def rename(fileName, newName, &continueOnExistsProc)
@zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
&continueOnExistsProc)
end
2006-02-23 06:44:28 +08:00
def mkdir(fileName, permissionInt = 0755)
@zipFile.mkdir(expand_to_entry(fileName), permissionInt)
end
# Turns entries into strings and adds leading /
# and removes trailing slash on directories
def each
@zipFile.each {
|e|
yield("/"+e.to_s.chomp("/"))
}
end
2011-11-16 23:09:14 +08:00
def expand_path(aPath)
2012-02-01 17:59:56 +08:00
expanded = aPath.start_with?("/") ? aPath : ::File.join(@pwd, aPath)
expanded.gsub!(/\/\.(\/|$)/, "")
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
expanded.empty? ? "/" : expanded
end
private
def expand_to_entry(aPath)
2012-02-01 18:02:56 +08:00
expand_path(aPath)[1..-1]
end
end
end
class File
include FileSystem
end
2002-07-26 22:12:34 +08:00
end
# Copyright (C) 2002, 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.