Applied extra-field patch
This commit is contained in:
parent
d024c87c47
commit
c3949e3e73
2
README
2
README
|
@ -45,3 +45,5 @@ http://rubyzip.sourceforge.net
|
|||
= Author
|
||||
|
||||
Thomas Sondergaard (thomas at thomassondergaard.com)
|
||||
|
||||
extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org)
|
||||
|
|
|
@ -173,9 +173,12 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase
|
|||
end
|
||||
|
||||
def test_utime
|
||||
assert_exception(StandardError, "utime not supported") {
|
||||
@zipFile.file.utime(100, "file1", "dir1")
|
||||
}
|
||||
t_now = Time.now
|
||||
t_bak = @zipFile.file.mtime("file1")
|
||||
@zipFile.file.utime(t_now, "file1")
|
||||
assert_equals(t_now, @zipFile.file.mtime("file1"))
|
||||
@zipFile.file.utime(t_bak, "file1")
|
||||
assert_equals(t_bak, @zipFile.file.mtime("file1"))
|
||||
end
|
||||
|
||||
|
||||
|
@ -256,7 +259,10 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase
|
|||
end
|
||||
|
||||
def test_chown
|
||||
assert_equals(2, @zipFile.file.chown(1,2, "noSuchFile", "file1"))
|
||||
assert_equals(2, @zipFile.file.chown(1,2, "dir1", "file1"))
|
||||
assert_equals(1, @zipFile.file.stat("dir1").uid)
|
||||
assert_equals(2, @zipFile.file.stat("dir1").gid)
|
||||
assert_equals(2, @zipFile.file.chown(nil, nil, "dir1", "file1"))
|
||||
end
|
||||
|
||||
def test_zero?
|
||||
|
@ -295,17 +301,17 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase
|
|||
end
|
||||
|
||||
def test_mtime
|
||||
assert_equals(Time.local(2002, "Jul", 26, 16, 38, 26),
|
||||
assert_equals(Time.at(1027694306),
|
||||
@zipFile.file.mtime("dir2/file21"))
|
||||
assert_equals(Time.local(2002, "Jul", 26, 15, 41, 04),
|
||||
assert_equals(Time.at(1027690863),
|
||||
@zipFile.file.mtime("dir2/dir21"))
|
||||
assert_exception(Errno::ENOENT) {
|
||||
@zipFile.file.mtime("noSuchEntry")
|
||||
}
|
||||
|
||||
assert_equals(Time.local(2002, "Jul", 26, 16, 38, 26),
|
||||
assert_equals(Time.at(1027694306),
|
||||
@zipFile.file.stat("dir2/file21").mtime)
|
||||
assert_equals(Time.local(2002, "Jul", 26, 15, 41, 04),
|
||||
assert_equals(Time.at(1027690863),
|
||||
@zipFile.file.stat("dir2/dir21").mtime)
|
||||
end
|
||||
|
||||
|
@ -320,35 +326,59 @@ class ZipFsFileNonmutatingTest < RUNIT::TestCase
|
|||
end
|
||||
|
||||
def test_readable?
|
||||
assert_true_if_entry_exists(:readable?)
|
||||
assert(! @zipFile.file.readable?("noSuchFile"))
|
||||
assert(@zipFile.file.readable?("file1"))
|
||||
assert(@zipFile.file.readable?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").readable?)
|
||||
assert(@zipFile.file.stat("dir1").readable?)
|
||||
end
|
||||
|
||||
def test_readable_real?
|
||||
assert_true_if_entry_exists(:readable_real?)
|
||||
assert(! @zipFile.file.readable_real?("noSuchFile"))
|
||||
assert(@zipFile.file.readable_real?("file1"))
|
||||
assert(@zipFile.file.readable_real?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").readable_real?)
|
||||
assert(@zipFile.file.stat("dir1").readable_real?)
|
||||
end
|
||||
|
||||
def test_writable?
|
||||
assert_true_if_entry_exists(:writable?)
|
||||
assert(! @zipFile.file.writable?("noSuchFile"))
|
||||
assert(@zipFile.file.writable?("file1"))
|
||||
assert(@zipFile.file.writable?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").writable?)
|
||||
assert(@zipFile.file.stat("dir1").writable?)
|
||||
end
|
||||
|
||||
def test_writable_real?
|
||||
assert_true_if_entry_exists(:writable_real?)
|
||||
assert(! @zipFile.file.writable_real?("noSuchFile"))
|
||||
assert(@zipFile.file.writable_real?("file1"))
|
||||
assert(@zipFile.file.writable_real?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").writable_real?)
|
||||
assert(@zipFile.file.stat("dir1").writable_real?)
|
||||
end
|
||||
|
||||
def test_executable?
|
||||
assert_true_if_entry_exists(:executable?)
|
||||
assert(! @zipFile.file.executable?("noSuchFile"))
|
||||
assert(! @zipFile.file.executable?("file1"))
|
||||
assert(@zipFile.file.executable?("dir1"))
|
||||
assert(! @zipFile.file.stat("file1").executable?)
|
||||
assert(@zipFile.file.stat("dir1").executable?)
|
||||
end
|
||||
|
||||
def test_executable_real?
|
||||
assert_true_if_entry_exists(:executable_real?)
|
||||
assert(! @zipFile.file.executable_real?("noSuchFile"))
|
||||
assert(! @zipFile.file.executable_real?("file1"))
|
||||
assert(@zipFile.file.executable_real?("dir1"))
|
||||
assert(! @zipFile.file.stat("file1").executable_real?)
|
||||
assert(@zipFile.file.stat("dir1").executable_real?)
|
||||
end
|
||||
|
||||
def test_owned?
|
||||
assert_true_if_entry_exists(:executable_real?)
|
||||
assert_true_if_entry_exists(:owned?)
|
||||
end
|
||||
|
||||
def test_grpowned?
|
||||
assert_true_if_entry_exists(:executable_real?)
|
||||
assert_true_if_entry_exists(:grpowned?)
|
||||
end
|
||||
|
||||
def test_setgid?
|
||||
|
@ -485,7 +515,10 @@ class ZipFsFileStatTest < RUNIT::TestCase
|
|||
end
|
||||
|
||||
def test_mode
|
||||
assert_equals(33206, @zipFile.file.stat("file1").mode)
|
||||
assert_equals(0600, @zipFile.file.stat("file1").mode & 0777)
|
||||
assert_equals(0600, @zipFile.file.stat("file1").mode & 0777)
|
||||
assert_equals(0755, @zipFile.file.stat("dir1").mode & 0777)
|
||||
assert_equals(0755, @zipFile.file.stat("dir1").mode & 0777)
|
||||
end
|
||||
|
||||
def test_dev
|
||||
|
|
|
@ -156,7 +156,7 @@ class ZipEntryTest < RUNIT::TestCase
|
|||
assert_equals(TEST_COMMENT, entry.comment)
|
||||
assert_equals(TEST_COMPRESSED_SIZE, entry.compressed_size)
|
||||
assert_equals(TEST_CRC, entry.crc)
|
||||
assert_equals(TEST_EXTRA, entry.extra)
|
||||
assert_instance_of(Zip::ZipExtraField, entry.extra)
|
||||
assert_equals(TEST_COMPRESSIONMETHOD, entry.compression_method)
|
||||
assert_equals(TEST_NAME, entry.name)
|
||||
assert_equals(TEST_SIZE, entry.size)
|
||||
|
@ -323,7 +323,7 @@ class ZipLocalEntryTest < RUNIT::TestCase
|
|||
|file|
|
||||
entry = ZipEntry.read_local_entry(file)
|
||||
assert_equals("zippedruby1.rb", entry.name)
|
||||
assert_equals(Time.local(2002, "Apr", 20, 02, 13, 58), entry.time)
|
||||
assert_equals(Time.at(1019261638), entry.time)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -1722,11 +1722,11 @@ class ZipFileExtractTest < CommonZipFileFixture
|
|||
gotCalledCorrectly = false
|
||||
ZipFile.open(TEST_ZIP.zip_name) {
|
||||
|zf|
|
||||
zf.extract(zf.entries.first, EXTRACTED_FILENAME) {
|
||||
|entry, extractLoc|
|
||||
gotCalledCorrectly = zf.entries.first == entry &&
|
||||
extractLoc == EXTRACTED_FILENAME
|
||||
true
|
||||
zf.extract(zf.entries.first, EXTRACTED_FILENAME) {
|
||||
|entry, extractLoc|
|
||||
gotCalledCorrectly = zf.entries.first == entry &&
|
||||
extractLoc == EXTRACTED_FILENAME
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1838,6 +1838,73 @@ END {
|
|||
exit if ARGV.index("recreateonly") != nil
|
||||
}
|
||||
|
||||
class ZipExtraFieldTest < RUNIT::TestCase
|
||||
def test_new
|
||||
extra_pure = ZipExtraField.new("")
|
||||
extra_withstr = ZipExtraField.new("foo")
|
||||
assert_instance_of(ZipExtraField, extra_pure)
|
||||
assert_instance_of(ZipExtraField, extra_withstr)
|
||||
end
|
||||
|
||||
def test_unknownfield
|
||||
extra = ZipExtraField.new("foo")
|
||||
assert_equals(extra["Unknown"], "foo")
|
||||
extra.merge("a")
|
||||
assert_equals(extra["Unknown"], "fooa")
|
||||
extra.merge("barbaz")
|
||||
assert_equals(extra.to_s, "fooabarbaz")
|
||||
end
|
||||
|
||||
|
||||
def test_merge
|
||||
str = "UT\x5\0\x3\250$\r@Ux\0\0"
|
||||
extra1 = ZipExtraField.new("")
|
||||
extra2 = ZipExtraField.new(str)
|
||||
assert(! extra1.member?("UniversalTime"))
|
||||
assert(extra2.member?("UniversalTime"))
|
||||
extra1.merge(str)
|
||||
assert_equals(extra1["UniversalTime"].mtime, extra2["UniversalTime"].mtime)
|
||||
end
|
||||
|
||||
def test_length
|
||||
str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit"
|
||||
extra = ZipExtraField.new(str)
|
||||
assert_equals(extra.local_length, extra.to_local_bin.length)
|
||||
assert_equals(extra.c_dir_length, extra.to_c_dir_bin.length)
|
||||
extra.merge("foo")
|
||||
assert_equals(extra.local_length, extra.to_local_bin.length)
|
||||
assert_equals(extra.c_dir_length, extra.to_c_dir_bin.length)
|
||||
end
|
||||
|
||||
|
||||
def test_to_s
|
||||
str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit"
|
||||
extra = ZipExtraField.new(str)
|
||||
assert_instance_of(String, extra.to_s)
|
||||
|
||||
s = extra.to_s
|
||||
extra.merge("foo")
|
||||
assert_equals(s.length + 3, extra.to_s.length)
|
||||
end
|
||||
|
||||
def test_equality
|
||||
str = "UT\x5\0\x3\250$\r@"
|
||||
extra1 = ZipExtraField.new(str)
|
||||
extra2 = ZipExtraField.new(str)
|
||||
extra3 = ZipExtraField.new(str)
|
||||
assert_equals(extra1, extra2)
|
||||
|
||||
extra2["UniversalTime"].mtime = Time.now
|
||||
assert(extra1 != extra2)
|
||||
|
||||
extra3.create("IUnix")
|
||||
assert(extra1 != extra3)
|
||||
|
||||
extra1.create("IUnix")
|
||||
assert_equals(extra1, extra3)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
|
|
|
@ -1,117 +1,112 @@
|
|||
unless Enumerable.instance_methods(true).include?("inject")
|
||||
module Enumerable #:nodoc:all
|
||||
def inject(n = 0)
|
||||
each { |value| n = yield(n, value) }
|
||||
n
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Enumerable #:nodoc:all
|
||||
# returns a new array of all the return values not equal to nil
|
||||
# This implementation could be faster
|
||||
def select_map(&aProc)
|
||||
map(&aProc).reject { |e| e.nil? }
|
||||
end
|
||||
end
|
||||
|
||||
unless Object.instance_methods(true).include?("object_id")
|
||||
class Object
|
||||
# Using object_id which is the new thing, so we need
|
||||
# to make that work in versions prior to 1.8.0
|
||||
alias object_id id
|
||||
end
|
||||
end
|
||||
|
||||
unless File.respond_to?(:read)
|
||||
class File
|
||||
# singleton method read does not exist in 1.6.x
|
||||
def self.read(fileName)
|
||||
open(fileName) { |f| f.read }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
def starts_with(aString)
|
||||
slice(0, aString.size) == aString
|
||||
end
|
||||
|
||||
def ends_with(aString)
|
||||
aStringSize = aString.size
|
||||
slice(-aStringSize, aStringSize) == aString
|
||||
end
|
||||
|
||||
def ensure_end(aString)
|
||||
ends_with(aString) ? self : self + aString
|
||||
end
|
||||
|
||||
def lchop
|
||||
slice(1, length)
|
||||
end
|
||||
end
|
||||
|
||||
class Time
|
||||
|
||||
#MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
|
||||
#
|
||||
# Register CX, the Time:
|
||||
# Bits 0-4 2 second increments (0-29)
|
||||
# Bits 5-10 minutes (0-59)
|
||||
# bits 11-15 hours (0-24)
|
||||
#
|
||||
# Register DX, the Date:
|
||||
# Bits 0-4 day (1-31)
|
||||
# bits 5-8 month (1-12)
|
||||
# bits 9-15 year (four digit year minus 1980)
|
||||
|
||||
|
||||
def to_binary_dos_time
|
||||
(sec/2) +
|
||||
(min << 5) +
|
||||
(hour << 11)
|
||||
end
|
||||
|
||||
def to_binary_dos_date
|
||||
(day) +
|
||||
(month << 5) +
|
||||
((year - 1980) << 9)
|
||||
end
|
||||
|
||||
# Dos time is only stored with two seconds accuracy
|
||||
def dos_equals(other)
|
||||
(year == other.year &&
|
||||
month == other.month &&
|
||||
day == other.day &&
|
||||
hour == other.hour &&
|
||||
min == other.min &&
|
||||
sec/2 == other.sec/2)
|
||||
end
|
||||
|
||||
def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
|
||||
second = 2 * ( 0b11111 & binaryDosTime)
|
||||
minute = ( 0b11111100000 & binaryDosTime) >> 5
|
||||
hour = (0b1111100000000000 & binaryDosTime) >> 11
|
||||
day = ( 0b11111 & binaryDosDate)
|
||||
month = ( 0b111100000 & binaryDosDate) >> 5
|
||||
year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
|
||||
begin
|
||||
return Time.local(year, month, day, hour, minute, second)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Module
|
||||
def forward_message(forwarder, *messagesToForward)
|
||||
methodDefs = messagesToForward.map {
|
||||
|msg|
|
||||
"def #{msg}; #{forwarder}(:#{msg}); end"
|
||||
}
|
||||
module_eval(methodDefs.join("\n"))
|
||||
end
|
||||
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.
|
||||
unless Enumerable.instance_methods(true).include?("inject")
|
||||
module Enumerable #:nodoc:all
|
||||
def inject(n = 0)
|
||||
each { |value| n = yield(n, value) }
|
||||
n
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Enumerable #:nodoc:all
|
||||
# returns a new array of all the return values not equal to nil
|
||||
# This implementation could be faster
|
||||
def select_map(&aProc)
|
||||
map(&aProc).reject { |e| e.nil? }
|
||||
end
|
||||
end
|
||||
|
||||
unless Object.instance_methods(true).include?("object_id")
|
||||
class Object
|
||||
# Using object_id which is the new thing, so we need
|
||||
# to make that work in versions prior to 1.8.0
|
||||
alias object_id id
|
||||
end
|
||||
end
|
||||
|
||||
unless File.respond_to?(:read)
|
||||
class File
|
||||
# singleton method read does not exist in 1.6.x
|
||||
def self.read(fileName)
|
||||
open(fileName) { |f| f.read }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
def starts_with(aString)
|
||||
slice(0, aString.size) == aString
|
||||
end
|
||||
|
||||
def ends_with(aString)
|
||||
aStringSize = aString.size
|
||||
slice(-aStringSize, aStringSize) == aString
|
||||
end
|
||||
|
||||
def ensure_end(aString)
|
||||
ends_with(aString) ? self : self + aString
|
||||
end
|
||||
|
||||
def lchop
|
||||
slice(1, length)
|
||||
end
|
||||
end
|
||||
|
||||
class Time
|
||||
|
||||
#MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
|
||||
#
|
||||
# Register CX, the Time:
|
||||
# Bits 0-4 2 second increments (0-29)
|
||||
# Bits 5-10 minutes (0-59)
|
||||
# bits 11-15 hours (0-24)
|
||||
#
|
||||
# Register DX, the Date:
|
||||
# Bits 0-4 day (1-31)
|
||||
# bits 5-8 month (1-12)
|
||||
# bits 9-15 year (four digit year minus 1980)
|
||||
|
||||
|
||||
def to_binary_dos_time
|
||||
(sec/2) +
|
||||
(min << 5) +
|
||||
(hour << 11)
|
||||
end
|
||||
|
||||
def to_binary_dos_date
|
||||
(day) +
|
||||
(month << 5) +
|
||||
((year - 1980) << 9)
|
||||
end
|
||||
|
||||
# Dos time is only stored with two seconds accuracy
|
||||
def dos_equals(other)
|
||||
to_i/2 == other.to_i/2
|
||||
end
|
||||
|
||||
def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
|
||||
second = 2 * ( 0b11111 & binaryDosTime)
|
||||
minute = ( 0b11111100000 & binaryDosTime) >> 5
|
||||
hour = (0b1111100000000000 & binaryDosTime) >> 11
|
||||
day = ( 0b11111 & binaryDosDate)
|
||||
month = ( 0b111100000 & binaryDosDate) >> 5
|
||||
year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
|
||||
begin
|
||||
return Time.local(year, month, day, hour, minute, second)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Module
|
||||
def forward_message(forwarder, *messagesToForward)
|
||||
methodDefs = messagesToForward.map {
|
||||
|msg|
|
||||
"def #{msg}; #{forwarder}(:#{msg}); end"
|
||||
}
|
||||
module_eval(methodDefs.join("\n"))
|
||||
end
|
||||
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.
|
||||
|
|
293
zip/zip.rb
293
zip/zip.rb
|
@ -316,12 +316,10 @@ module Zip
|
|||
DEFLATED = 8
|
||||
|
||||
attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
|
||||
:name, :size, :localHeaderOffset, :time, :zipfile
|
||||
:name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes
|
||||
|
||||
alias :mtime :time
|
||||
|
||||
def initialize(zipfile = "", name = "", comment = "", extra = "",
|
||||
compressed_size = 0, crc = 0,
|
||||
compressed_size = 0, crc = 0,
|
||||
compression_method = ZipEntry::DEFLATED, size = 0,
|
||||
time = Time.now)
|
||||
super()
|
||||
|
@ -329,12 +327,38 @@ module Zip
|
|||
raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
||||
end
|
||||
@localHeaderOffset = 0
|
||||
@internalFileAttributes = 1
|
||||
@externalFileAttributes = 0
|
||||
@version = 52 # this library's version
|
||||
@fstype = 0 # default is fat
|
||||
@zipfile, @comment, @compressed_size, @crc, @extra, @compression_method,
|
||||
@name, @size = zipfile, comment, compressed_size, crc,
|
||||
extra, compression_method, name, size
|
||||
@time = time
|
||||
unless ZipExtraField === @extra
|
||||
@extra = ZipExtraField.new(@extra.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def time
|
||||
if @extra["UniversalTime"]
|
||||
@extra["UniversalTime"].mtime
|
||||
else
|
||||
# Atandard time field in central directory has local time
|
||||
# under archive creator. Then, we can't get timezone.
|
||||
@time
|
||||
end
|
||||
end
|
||||
alias :mtime :time
|
||||
|
||||
def time=(aTime)
|
||||
unless @extra.member?("UniversalTime")
|
||||
@extra.create("UniversalTime")
|
||||
end
|
||||
@extra["UniversalTime"].mtime = aTime
|
||||
@time = aTime
|
||||
end
|
||||
|
||||
def directory?
|
||||
return (%r{\/$} =~ @name) != nil
|
||||
end
|
||||
|
@ -349,12 +373,12 @@ module Zip
|
|||
end
|
||||
|
||||
def local_header_size #:nodoc:all
|
||||
LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.size : 0)
|
||||
LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0)
|
||||
end
|
||||
|
||||
def cdir_header_size #:nodoc:all
|
||||
CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) +
|
||||
(@extra ? @extra.size : 0) + (@comment ? @comment.size : 0)
|
||||
(@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0)
|
||||
end
|
||||
|
||||
def next_header_offset #:nodoc:all
|
||||
|
@ -387,7 +411,8 @@ module Zip
|
|||
end
|
||||
|
||||
localHeader ,
|
||||
@version ,
|
||||
@version ,
|
||||
@fstype ,
|
||||
@gpFlags ,
|
||||
@compression_method,
|
||||
lastModTime ,
|
||||
|
@ -396,7 +421,7 @@ module Zip
|
|||
@compressed_size ,
|
||||
@size ,
|
||||
nameLength ,
|
||||
extraLength = staticSizedFieldsBuf.unpack('VvvvvvVVVvv')
|
||||
extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
|
||||
|
||||
unless (localHeader == LOCAL_ENTRY_SIGNATURE)
|
||||
raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
|
||||
|
@ -404,9 +429,16 @@ module Zip
|
|||
set_time(lastModDate, lastModTime)
|
||||
|
||||
@name = io.read(nameLength)
|
||||
@extra = io.read(extraLength)
|
||||
unless (@extra && @extra.length == extraLength)
|
||||
extra = io.read(extraLength)
|
||||
|
||||
if (extra && extra.length != extraLength)
|
||||
raise ZipError, "Truncated local zip entry header"
|
||||
else
|
||||
if ZipExtraField === @extra
|
||||
@extra.merge(extra)
|
||||
else
|
||||
@extra = ZipExtraField.new(extra)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -423,7 +455,8 @@ module Zip
|
|||
|
||||
io <<
|
||||
[LOCAL_ENTRY_SIGNATURE ,
|
||||
0 , # @version ,
|
||||
@version ,
|
||||
@fstype ,
|
||||
0 , # @gpFlags ,
|
||||
@compression_method ,
|
||||
@time.to_binary_dos_time , # @lastModTime ,
|
||||
|
@ -432,9 +465,9 @@ module Zip
|
|||
@compressed_size ,
|
||||
@size ,
|
||||
@name ? @name.length : 0,
|
||||
@extra? @extra.length : 0 ].pack('VvvvvvVVVvv')
|
||||
@extra? @extra.local_length : 0 ].pack('VCCvvvvVVVvv')
|
||||
io << @name
|
||||
io << @extra
|
||||
io << (@extra ? @extra.to_local_bin : "")
|
||||
end
|
||||
|
||||
CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
|
||||
|
@ -447,7 +480,8 @@ module Zip
|
|||
end
|
||||
|
||||
cdirSignature ,
|
||||
@version ,
|
||||
@version , # version of encoding software
|
||||
@fstype , # filesystem tye
|
||||
@versionNeededToExtract,
|
||||
@gpFlags ,
|
||||
@compression_method ,
|
||||
|
@ -465,7 +499,7 @@ module Zip
|
|||
@localHeaderOffset ,
|
||||
@name ,
|
||||
@extra ,
|
||||
@comment = staticSizedFieldsBuf.unpack('VvvvvvvVVVvvvvvVV')
|
||||
@comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
|
||||
|
||||
unless (cdirSignature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
|
||||
raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
|
||||
|
@ -473,7 +507,11 @@ module Zip
|
|||
set_time(lastModDate, lastModTime)
|
||||
|
||||
@name = io.read(nameLength)
|
||||
@extra = io.read(extraLength)
|
||||
if ZipExtraField === @extra
|
||||
@extra.merge(io.read(extraLength))
|
||||
else
|
||||
@extra = ZipExtraField.new(io.read(extraLength))
|
||||
end
|
||||
@comment = io.read(commentLength)
|
||||
unless (@comment && @comment.length == commentLength)
|
||||
raise ZipError, "Truncated cdir zip entry header"
|
||||
|
@ -492,7 +530,8 @@ module Zip
|
|||
def write_c_dir_entry(io) #:nodoc:all
|
||||
io <<
|
||||
[CENTRAL_DIRECTORY_ENTRY_SIGNATURE,
|
||||
0 , # @version ,
|
||||
@version , # version of encoding software
|
||||
@fstype , # filesystem type
|
||||
0 , # @versionNeededToExtract ,
|
||||
0 , # @gpFlags ,
|
||||
@compression_method ,
|
||||
|
@ -502,18 +541,18 @@ module Zip
|
|||
@compressed_size ,
|
||||
@size ,
|
||||
@name ? @name.length : 0 ,
|
||||
@extra ? @extra.length : 0 ,
|
||||
@extra ? @extra.c_dir_length : 0 ,
|
||||
@comment ? comment.length : 0 ,
|
||||
0 , # disk number start
|
||||
0 , # @internalFileAttributes ,
|
||||
0 , # @externalFileAttributes ,
|
||||
@internalFileAttributes , # file type (binary=0, text=1)
|
||||
@externalFileAttributes , # native filesystem attributes
|
||||
@localHeaderOffset ,
|
||||
@name ,
|
||||
@extra ,
|
||||
@comment ].pack('VvvvvvvVVVvvvvvVV')
|
||||
@comment ].pack('VCCvvvvvVVVvvvvvVV')
|
||||
|
||||
io << @name
|
||||
io << @extra
|
||||
io << (@extra ? @extra.to_c_dir_bin : "")
|
||||
io << @comment
|
||||
end
|
||||
|
||||
|
@ -526,7 +565,7 @@ module Zip
|
|||
@size == other.size &&
|
||||
@name == other.name &&
|
||||
@extra == other.extra &&
|
||||
@time.dos_equals(other.time))
|
||||
self.time.dos_equals(other.time))
|
||||
end
|
||||
|
||||
def <=> (other)
|
||||
|
@ -1212,6 +1251,214 @@ module Zip
|
|||
end
|
||||
end
|
||||
|
||||
class ZipExtraField < Hash
|
||||
ID_MAP = {}
|
||||
|
||||
# Meta class for extra fields
|
||||
class Generic
|
||||
def self.register_map
|
||||
if self.const_defined?(:HEADER_ID)
|
||||
ID_MAP[self.const_get(:HEADER_ID)] = self
|
||||
end
|
||||
end
|
||||
|
||||
def self.name
|
||||
self.to_s.split("::")[-1]
|
||||
end
|
||||
|
||||
# return field [size, content] or false
|
||||
def initial_parse(binstr)
|
||||
if ! binstr
|
||||
# If nil, start with empty.
|
||||
return false
|
||||
elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
|
||||
$stderr.puts "Warning: weired extra feild header ID. skip parsing"
|
||||
return false
|
||||
end
|
||||
[binstr[2,2].unpack("v")[0], binstr[4..-1]]
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class != other.class and return false
|
||||
each { |k, v|
|
||||
v != other[k] and return false
|
||||
}
|
||||
true
|
||||
end
|
||||
|
||||
def to_local_bin
|
||||
s = pack_for_local
|
||||
self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
|
||||
end
|
||||
|
||||
def to_c_dir_bin
|
||||
s = pack_for_c_dir
|
||||
self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
|
||||
end
|
||||
end
|
||||
|
||||
# Info-ZIP Additional timestamp field
|
||||
class UniversalTime < Generic
|
||||
HEADER_ID = "UT"
|
||||
register_map
|
||||
|
||||
def initialize(binstr = nil)
|
||||
@ctime = nil
|
||||
@mtime = nil
|
||||
@atime = nil
|
||||
@flag = nil
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
attr_accessor :atime, :ctime, :mtime, :flag
|
||||
|
||||
def merge(binstr)
|
||||
binstr == "" and return
|
||||
size, content = initial_parse(binstr)
|
||||
size or return
|
||||
@flag, mtime, atime, ctime = content.unpack("CVVV")
|
||||
mtime and @mtime ||= Time.at(mtime)
|
||||
atime and @atime ||= Time.at(atime)
|
||||
ctime and @ctime ||= Time.at(ctime)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@mtime == other.mtime &&
|
||||
@atime == other.atime &&
|
||||
@ctime == other.ctime
|
||||
end
|
||||
|
||||
def pack_for_local
|
||||
s = [@flag].pack("C")
|
||||
@flag & 1 != 0 and s << [@mtime.to_i].pack("V")
|
||||
@flag & 2 != 0 and s << [@atime.to_i].pack("V")
|
||||
@flag & 4 != 0 and s << [@ctime.to_i].pack("V")
|
||||
s
|
||||
end
|
||||
|
||||
def pack_for_c_dir
|
||||
s = [@flag].pack("C")
|
||||
@flag & 1 == 1 and s << [@mtime.to_i].pack("V")
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
# Info-ZIP Extra for UNIX uid/gid
|
||||
class IUnix < Generic
|
||||
HEADER_ID = "Ux"
|
||||
register_map
|
||||
|
||||
def initialize(binstr = nil)
|
||||
@uid = nil
|
||||
@gid = nil
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
attr_accessor :uid, :gid
|
||||
|
||||
def merge(binstr)
|
||||
binstr == "" and return
|
||||
size, content = initial_parse(binstr)
|
||||
# size: 0 for central direcotry. 4 for local header
|
||||
return if(! size || size == 0)
|
||||
uid, gid = content.unpack("vv")
|
||||
@uid ||= uid
|
||||
@gid ||= gid
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@uid == other.uid &&
|
||||
@gid == other.gid
|
||||
end
|
||||
|
||||
def pack_for_local
|
||||
[@uid, @gid].pack("vv")
|
||||
end
|
||||
|
||||
def pack_for_c_dir
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
## start main of ZipExtraField < Hash
|
||||
def initialize(binstr = nil)
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
|
||||
def merge(binstr)
|
||||
binstr == "" and return
|
||||
i = 0
|
||||
while i < binstr.length
|
||||
id = binstr[i,2]
|
||||
len = binstr[i+2,2].to_s.unpack("v")[0]
|
||||
if id && ID_MAP.member?(id)
|
||||
field_name = ID_MAP[id].name
|
||||
if self.member?(field_name)
|
||||
self[field_name].mergea(binstr[i, len+4])
|
||||
else
|
||||
field_obj = ID_MAP[id].new(binstr[i, len+4])
|
||||
self[field_name] = field_obj
|
||||
end
|
||||
elsif id
|
||||
unless self["Unknown"]
|
||||
s = ""
|
||||
class << s
|
||||
alias_method :to_c_dir_bin, :to_s
|
||||
alias_method :to_local_bin, :to_s
|
||||
end
|
||||
self["Unknown"] = s
|
||||
end
|
||||
if ! len || len+4 > binstr[i..-1].length
|
||||
self["Unknown"] << binstr[i..-1]
|
||||
break;
|
||||
end
|
||||
self["Unknown"] << binstr[i, len+4]
|
||||
end
|
||||
i += len+4
|
||||
end
|
||||
end
|
||||
|
||||
def create(name)
|
||||
field_class = nil
|
||||
ID_MAP.each { |id, klass|
|
||||
if klass.name == name
|
||||
field_class = klass
|
||||
break
|
||||
end
|
||||
}
|
||||
if ! field_class
|
||||
raise ZipError, "Unknown extra field '#{name}'"
|
||||
end
|
||||
self[name] = field_class.new()
|
||||
end
|
||||
|
||||
def to_local_bin
|
||||
s = ""
|
||||
each { |k, v|
|
||||
s << v.to_local_bin
|
||||
}
|
||||
s
|
||||
end
|
||||
alias :to_s :to_local_bin
|
||||
|
||||
def to_c_dir_bin
|
||||
s = ""
|
||||
each { |k, v|
|
||||
s << v.to_c_dir_bin
|
||||
}
|
||||
s
|
||||
end
|
||||
|
||||
def c_dir_length
|
||||
to_c_dir_bin.length
|
||||
end
|
||||
def local_length
|
||||
to_local_bin.length
|
||||
end
|
||||
alias :c_dir_size :c_dir_length
|
||||
alias :local_size :local_length
|
||||
alias :length :local_length
|
||||
alias :size :local_length
|
||||
end # end ZipExtraField
|
||||
|
||||
end # Zip namespace module
|
||||
|
||||
|
||||
|
|
|
@ -53,9 +53,28 @@ module Zip
|
|||
|
||||
def blocks; nil; end
|
||||
|
||||
def gid; 0; end
|
||||
def get_entry
|
||||
@zipFsFile.__send__(:get_entry, @entryName)
|
||||
end
|
||||
private :get_entry
|
||||
|
||||
def uid; 0; end
|
||||
def gid
|
||||
e = get_entry
|
||||
if e.extra.member? "IUnix"
|
||||
e.extra["IUnix"].gid || 0
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def uid
|
||||
e = get_entry
|
||||
if e.extra.member? "IUnix"
|
||||
e.extra["IUnix"].uid || 0
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def ino; 0; end
|
||||
|
||||
|
@ -81,12 +100,37 @@ module Zip
|
|||
|
||||
def blksize; nil; end
|
||||
|
||||
def mode; 33206; end # 33206 is equivalent to -rw-rw-rw-
|
||||
def mode
|
||||
e = get_entry
|
||||
if e.fstype == 3
|
||||
e.externalFileAttributes >> 16
|
||||
else
|
||||
33206 # 33206 is equivalent to -rw-rw-rw-
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(mappedZip)
|
||||
@mappedZip = mappedZip
|
||||
end
|
||||
|
||||
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)
|
||||
e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
|
||||
rescue Errno::ENOENT
|
||||
false
|
||||
end
|
||||
end
|
||||
private :unix_mode_cmp
|
||||
|
||||
def exists?(fileName)
|
||||
expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
|
||||
|
@ -94,25 +138,34 @@ module Zip
|
|||
alias :exist? :exists?
|
||||
|
||||
# Permissions not implemented, so if the file exists it is accessible
|
||||
alias readable? exists?
|
||||
alias readable_real? exists?
|
||||
alias writable? exists?
|
||||
alias writable_real? exists?
|
||||
alias executable? exists?
|
||||
alias executable_real? exists?
|
||||
alias owned? exists?
|
||||
alias grpowned? exists?
|
||||
|
||||
def readable?(fileName)
|
||||
unix_mode_cmp(fileName, 0444)
|
||||
end
|
||||
alias readable_real? readable?
|
||||
|
||||
def writable?(fileName)
|
||||
unix_mode_cmp(fileName, 0222)
|
||||
end
|
||||
alias writable_real? writable?
|
||||
|
||||
def executable?(fileName)
|
||||
unix_mode_cmp(fileName, 0111)
|
||||
end
|
||||
alias executable_real? executable?
|
||||
|
||||
def setuid?(fileName)
|
||||
false
|
||||
unix_mode_cmp(fileName, 04000)
|
||||
end
|
||||
|
||||
def setgid?(fileName)
|
||||
false
|
||||
unix_mode_cmp(fileName, 02000)
|
||||
end
|
||||
|
||||
def sticky?(fileName)
|
||||
false
|
||||
unix_mode_cmp(fileName, 01000)
|
||||
end
|
||||
|
||||
def umask(*args)
|
||||
|
@ -153,16 +206,23 @@ module Zip
|
|||
return (entry == nil || entry.directory?) ? nil : entry.size
|
||||
end
|
||||
|
||||
def chown(ownerInt, groupInt, *filenames)
|
||||
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)
|
||||
filenames.each {
|
||||
|elem|
|
||||
if ! exists?(elem)
|
||||
raise Errno::ENOENT, "No such file or directory - #{elem}"
|
||||
end
|
||||
filenames.each { |fileName|
|
||||
e = get_entry(fileName)
|
||||
e.fstype = 3 # force convertion filesystem type to unix
|
||||
e.externalFileAttributes = modeInt << 16
|
||||
}
|
||||
filenames.size
|
||||
end
|
||||
|
@ -195,8 +255,10 @@ module Zip
|
|||
::File.join(*fragments)
|
||||
end
|
||||
|
||||
def utime(accessTime, *fileNames)
|
||||
raise StandardError, "utime not supported"
|
||||
def utime(modifiedTime, *fileNames)
|
||||
fileNames.each { |fileName|
|
||||
get_entry(fileName).time = modifiedTime
|
||||
}
|
||||
end
|
||||
|
||||
def mtime(fileName)
|
||||
|
@ -204,13 +266,21 @@ module Zip
|
|||
end
|
||||
|
||||
def atime(fileName)
|
||||
@mappedZip.get_entry(fileName)
|
||||
nil
|
||||
e = get_entry(fileName)
|
||||
if e.extra.member? "UniversalTime"
|
||||
e.extra["UniversalTime"].atime
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def ctime(fileName)
|
||||
@mappedZip.get_entry(fileName)
|
||||
nil
|
||||
e = get_entry(fileName)
|
||||
if e.extra.member? "UniversalTime"
|
||||
e.extra["UniversalTime"].ctime
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def pipe?(filename)
|
||||
|
|
Loading…
Reference in New Issue