NTFS Extra Field (0x000a) support
This commit is contained in:
parent
8ede42436e
commit
a7a11e33bd
|
@ -74,6 +74,8 @@ module Zip
|
||||||
def time
|
def time
|
||||||
if @extra['UniversalTime']
|
if @extra['UniversalTime']
|
||||||
@extra['UniversalTime'].mtime
|
@extra['UniversalTime'].mtime
|
||||||
|
elsif @extra['NTFS']
|
||||||
|
@extra['NTFS'].mtime
|
||||||
else
|
else
|
||||||
# Standard time field in central directory has local time
|
# Standard time field in central directory has local time
|
||||||
# under archive creator. Then, we can't get timezone.
|
# under archive creator. Then, we can't get timezone.
|
||||||
|
@ -84,10 +86,10 @@ module Zip
|
||||||
alias :mtime :time
|
alias :mtime :time
|
||||||
|
|
||||||
def time=(value)
|
def time=(value)
|
||||||
unless @extra.member?('UniversalTime')
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
||||||
@extra.create('UniversalTime')
|
@extra.create('UniversalTime')
|
||||||
end
|
end
|
||||||
@extra['UniversalTime'].mtime = value
|
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
||||||
@time = value
|
@time = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ require 'zip/extra_field/old_unix'
|
||||||
require 'zip/extra_field/unix'
|
require 'zip/extra_field/unix'
|
||||||
require 'zip/extra_field/zip64'
|
require 'zip/extra_field/zip64'
|
||||||
require 'zip/extra_field/zip64_placeholder'
|
require 'zip/extra_field/zip64_placeholder'
|
||||||
|
require 'zip/extra_field/ntfs'
|
||||||
|
|
||||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||||
# rubyzip is free software; you can redistribute it and/or
|
# rubyzip is free software; you can redistribute it and/or
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
module Zip
|
||||||
|
# PKWARE NTFS Extra Field (0x000a)
|
||||||
|
# Only Tag 0x0001 is supported
|
||||||
|
class ExtraField::NTFS < ExtraField::Generic
|
||||||
|
HEADER_ID = [0x000A].pack('v')
|
||||||
|
register_map
|
||||||
|
|
||||||
|
WINDOWS_TICK = 10000000.0
|
||||||
|
SEC_TO_UNIX_EPOCH = 11644473600
|
||||||
|
|
||||||
|
def initialize(binstr = nil)
|
||||||
|
@ctime = nil
|
||||||
|
@mtime = nil
|
||||||
|
@atime = nil
|
||||||
|
binstr and merge(binstr)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :atime, :ctime, :mtime
|
||||||
|
|
||||||
|
def merge(binstr)
|
||||||
|
return if binstr.empty?
|
||||||
|
size, content = initial_parse(binstr)
|
||||||
|
(size && content) or return
|
||||||
|
|
||||||
|
content = content[4..-1]
|
||||||
|
tags = parse_tags(content)
|
||||||
|
|
||||||
|
tag1 = tags[1]
|
||||||
|
if tag1
|
||||||
|
ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack("Q<Q<Q<")
|
||||||
|
ntfs_mtime and @mtime ||= from_ntfs_time(ntfs_mtime)
|
||||||
|
ntfs_atime and @atime ||= from_ntfs_time(ntfs_atime)
|
||||||
|
ntfs_ctime and @ctime ||= from_ntfs_time(ntfs_ctime)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
@mtime == other.mtime &&
|
||||||
|
@atime == other.atime &&
|
||||||
|
@ctime == other.ctime
|
||||||
|
end
|
||||||
|
|
||||||
|
# Info-ZIP note states this extra field is stored at local header
|
||||||
|
def pack_for_local
|
||||||
|
pack_for_c_dir
|
||||||
|
end
|
||||||
|
|
||||||
|
# But 7-zip for Windows only stores at central dir
|
||||||
|
def pack_for_c_dir
|
||||||
|
# reserved 0 and tag 1
|
||||||
|
s = [0, 1].pack("Vv")
|
||||||
|
|
||||||
|
tag1 = ''.force_encoding(Encoding::BINARY)
|
||||||
|
if @mtime
|
||||||
|
tag1 << [to_ntfs_time(@mtime)].pack('Q<')
|
||||||
|
if @atime
|
||||||
|
tag1 << [to_ntfs_time(@atime)].pack('Q<')
|
||||||
|
if @ctime
|
||||||
|
tag1 << [to_ntfs_time(@ctime)].pack('Q<')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
s << [tag1.bytesize].pack('v') << tag1
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def parse_tags(content)
|
||||||
|
return {} if content.nil?
|
||||||
|
tags = {}
|
||||||
|
i = 0
|
||||||
|
while i < content.bytesize do
|
||||||
|
tag, size = content[i, 4].unpack('vv')
|
||||||
|
i += 4
|
||||||
|
break unless tag && size
|
||||||
|
value = content[i, size]
|
||||||
|
i += size
|
||||||
|
tags[tag] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
tags
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_ntfs_time(ntfs_time)
|
||||||
|
::Zip::DOSTime.at(ntfs_time / WINDOWS_TICK - SEC_TO_UNIX_EPOCH)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_ntfs_time(time)
|
||||||
|
((time.to_f + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK).to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -320,6 +320,8 @@ module Zip
|
||||||
e = get_entry(fileName)
|
e = get_entry(fileName)
|
||||||
if e.extra.member? "UniversalTime"
|
if e.extra.member? "UniversalTime"
|
||||||
e.extra["UniversalTime"].atime
|
e.extra["UniversalTime"].atime
|
||||||
|
elsif e.extra.member? "NTFS"
|
||||||
|
e.extra["NTFS"].atime
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -329,6 +331,8 @@ module Zip
|
||||||
e = get_entry(fileName)
|
e = get_entry(fileName)
|
||||||
if e.extra.member? "UniversalTime"
|
if e.extra.member? "UniversalTime"
|
||||||
e.extra["UniversalTime"].ctime
|
e.extra["UniversalTime"].ctime
|
||||||
|
elsif e.extra.member? "NTFS"
|
||||||
|
e.extra["NTFS"].ctime
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
Binary file not shown.
|
@ -17,6 +17,15 @@ class ZipExtraFieldTest < MiniTest::Test
|
||||||
assert_equal(extra.to_s, "fooabarbaz")
|
assert_equal(extra.to_s, "fooabarbaz")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ntfs
|
||||||
|
str = "\x0A\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01\xC0\x81\x17\xE8B\xCE\xCF\x01"
|
||||||
|
extra = ::Zip::ExtraField.new(str)
|
||||||
|
assert(extra.member?("NTFS"))
|
||||||
|
t = ::Zip::DOSTime.at(1410496497.405178)
|
||||||
|
assert_equal(t, extra['NTFS'].mtime)
|
||||||
|
assert_equal(t, extra['NTFS'].atime)
|
||||||
|
assert_equal(t, extra['NTFS'].ctime)
|
||||||
|
end
|
||||||
|
|
||||||
def test_merge
|
def test_merge
|
||||||
str = "UT\x5\0\x3\250$\r@Ux\0\0"
|
str = "UT\x5\0\x3\250$\r@Ux\0\0"
|
||||||
|
|
|
@ -307,6 +307,15 @@ class ZipFsFileNonmutatingTest < MiniTest::Test
|
||||||
assert_nil(@zip_file.file.stat("file1").atime)
|
assert_nil(@zip_file.file.stat("file1").atime)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ntfs_time
|
||||||
|
::Zip::File.open("test/data/ntfs.zip") do |zf|
|
||||||
|
t = ::Zip::DOSTime.at(1410496497.405178)
|
||||||
|
assert_equal(zf.file.mtime("data.txt"), t)
|
||||||
|
assert_equal(zf.file.atime("data.txt"), t)
|
||||||
|
assert_equal(zf.file.ctime("data.txt"), t)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_readable?
|
def test_readable?
|
||||||
assert(! @zip_file.file.readable?("noSuchFile"))
|
assert(! @zip_file.file.readable?("noSuchFile"))
|
||||||
assert(@zip_file.file.readable?("file1"))
|
assert(@zip_file.file.readable?("file1"))
|
||||||
|
|
Loading…
Reference in New Issue