NTFS Extra Field (0x000a) support

This commit is contained in:
Henry Yang 2014-09-11 23:04:23 -07:00
parent 8ede42436e
commit a7a11e33bd
7 changed files with 119 additions and 2 deletions

View File

@ -74,6 +74,8 @@ module Zip
def time
if @extra['UniversalTime']
@extra['UniversalTime'].mtime
elsif @extra['NTFS']
@extra['NTFS'].mtime
else
# Standard time field in central directory has local time
# under archive creator. Then, we can't get timezone.
@ -84,10 +86,10 @@ module Zip
alias :mtime :time
def time=(value)
unless @extra.member?('UniversalTime')
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
@extra.create('UniversalTime')
end
@extra['UniversalTime'].mtime = value
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
@time = value
end

View File

@ -94,6 +94,7 @@ require 'zip/extra_field/old_unix'
require 'zip/extra_field/unix'
require 'zip/extra_field/zip64'
require 'zip/extra_field/zip64_placeholder'
require 'zip/extra_field/ntfs'
# Copyright (C) 2002, 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or

View File

@ -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

View File

@ -320,6 +320,8 @@ module Zip
e = get_entry(fileName)
if e.extra.member? "UniversalTime"
e.extra["UniversalTime"].atime
elsif e.extra.member? "NTFS"
e.extra["NTFS"].atime
else
nil
end
@ -329,6 +331,8 @@ module Zip
e = get_entry(fileName)
if e.extra.member? "UniversalTime"
e.extra["UniversalTime"].ctime
elsif e.extra.member? "NTFS"
e.extra["NTFS"].ctime
else
nil
end

BIN
test/data/ntfs.zip Normal file

Binary file not shown.

View File

@ -17,6 +17,15 @@ class ZipExtraFieldTest < MiniTest::Test
assert_equal(extra.to_s, "fooabarbaz")
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
str = "UT\x5\0\x3\250$\r@Ux\0\0"

View File

@ -307,6 +307,15 @@ class ZipFsFileNonmutatingTest < MiniTest::Test
assert_nil(@zip_file.file.stat("file1").atime)
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?
assert(! @zip_file.file.readable?("noSuchFile"))
assert(@zip_file.file.readable?("file1"))