From a7a11e33bdb27f5af00b1246f46729e7cd959958 Mon Sep 17 00:00:00 2001 From: Henry Yang Date: Thu, 11 Sep 2014 23:04:23 -0700 Subject: [PATCH] NTFS Extra Field (0x000a) support --- lib/zip/entry.rb | 6 +- lib/zip/extra_field.rb | 1 + lib/zip/extra_field/ntfs.rb | 92 +++++++++++++++++++++++ lib/zip/filesystem.rb | 4 + test/data/ntfs.zip | Bin 0 -> 165 bytes test/extra_field_test.rb | 9 +++ test/filesystem/file_nonmutating_test.rb | 9 +++ 7 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 lib/zip/extra_field/ntfs.rb create mode 100644 test/data/ntfs.zip diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 2fa8bac..40a370a 100755 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -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 diff --git a/lib/zip/extra_field.rb b/lib/zip/extra_field.rb index f7a7172..d8b5050 100755 --- a/lib/zip/extra_field.rb +++ b/lib/zip/extra_field.rb @@ -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 diff --git a/lib/zip/extra_field/ntfs.rb b/lib/zip/extra_field/ntfs.rb new file mode 100644 index 0000000..9feabd4 --- /dev/null +++ b/lib/zip/extra_field/ntfs.rb @@ -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("Qr zGcuVlTt5Hi#NVhA1Gz6vSZw E0NiLGPXGV_ literal 0 HcmV?d00001 diff --git a/test/extra_field_test.rb b/test/extra_field_test.rb index dd0a022..c138a5d 100644 --- a/test/extra_field_test.rb +++ b/test/extra_field_test.rb @@ -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" diff --git a/test/filesystem/file_nonmutating_test.rb b/test/filesystem/file_nonmutating_test.rb index f764e78..b03dbef 100644 --- a/test/filesystem/file_nonmutating_test.rb +++ b/test/filesystem/file_nonmutating_test.rb @@ -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"))