Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for the UniversalTime extra field #421

Merged
merged 6 commits into from Dec 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 39 additions & 12 deletions lib/zip/extra_field/universal_time.rb
Expand Up @@ -4,24 +4,51 @@ class ExtraField::UniversalTime < ExtraField::Generic
HEADER_ID = 'UT'
register_map

ATIME_MASK = 0b010
CTIME_MASK = 0b100
MTIME_MASK = 0b001

def initialize(binstr = nil)
@ctime = nil
@mtime = nil
@atime = nil
@flag = nil
binstr && merge(binstr)
@flag = 0

merge(binstr) unless binstr.nil?
end

attr_reader :atime, :ctime, :mtime, :flag

def atime=(time)
@flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
@atime = time
end

attr_accessor :atime, :ctime, :mtime, :flag
def ctime=(time)
@flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
@ctime = time
end

def mtime=(time)
@flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
@mtime = time
end

def merge(binstr)
return if binstr.empty?

size, content = initial_parse(binstr)
size || return
@flag, mtime, atime, ctime = content.unpack('CVVV')
mtime && @mtime ||= ::Zip::DOSTime.at(mtime)
atime && @atime ||= ::Zip::DOSTime.at(atime)
ctime && @ctime ||= ::Zip::DOSTime.at(ctime)
return if !size || size <= 0

@flag, *times = content.unpack('Cl<l<l<')

# Parse the timestamps, in order, based on which flags are set.
return if times[0].nil?
@mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
return if times[0].nil?
@atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
return if times[0].nil?
@ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
end

def ==(other)
Expand All @@ -32,15 +59,15 @@ def ==(other)

def pack_for_local
s = [@flag].pack('C')
@flag & 1 != 0 && s << [@mtime.to_i].pack('V')
@flag & 2 != 0 && s << [@atime.to_i].pack('V')
@flag & 4 != 0 && s << [@ctime.to_i].pack('V')
s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
s
end

def pack_for_c_dir
s = [@flag].pack('C')
@flag & 1 == 1 && s << [@mtime.to_i].pack('V')
s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
s
end
end
Expand Down
97 changes: 97 additions & 0 deletions test/extra_field_ut_test.rb
@@ -0,0 +1,97 @@
require 'test_helper'

class ZipExtraFieldUTTest < MiniTest::Test

PARSE_TESTS = [
["UT\x05\x00\x01PS>A", 0b001, true, true, false],
["UT\x05\x00\x02PS>A", 0b010, false, true, true],
["UT\x05\x00\x04PS>A", 0b100, true, false, true],
["UT\x09\x00\x03PS>APS>A", 0b011, false, true, false],
["UT\x09\x00\x05PS>APS>A", 0b101, true, false, false],
["UT\x09\x00\x06PS>APS>A", 0b110, false, false, true],
["UT\x13\x00\x07PS>APS>APS>A", 0b111, false, false, false]
]

def test_parse
PARSE_TESTS.each do |bin, flags, a, c, m|
ut = ::Zip::ExtraField::UniversalTime.new(bin)
assert_equal(flags, ut.flag)
assert(ut.atime.nil? == a)
assert(ut.ctime.nil? == c)
assert(ut.mtime.nil? == m)
end
end

def test_parse_size_zero
ut = ::Zip::ExtraField::UniversalTime.new("UT\x00")
assert_equal(0b000, ut.flag)
assert_nil(ut.atime)
assert_nil(ut.ctime)
assert_nil(ut.mtime)
end

def test_parse_size_nil
ut = ::Zip::ExtraField::UniversalTime.new('UT')
assert_equal(0b000, ut.flag)
assert_nil(ut.atime)
assert_nil(ut.ctime)
assert_nil(ut.mtime)
end

def test_parse_nil
ut = ::Zip::ExtraField::UniversalTime.new
assert_equal(0b000, ut.flag)
assert_nil(ut.atime)
assert_nil(ut.ctime)
assert_nil(ut.mtime)
end

def test_set_clear_times
time = ::Zip::DOSTime.now
ut = ::Zip::ExtraField::UniversalTime.new
assert_equal(0b000, ut.flag)

ut.mtime = time
assert_equal(0b001, ut.flag)
assert_equal(time, ut.mtime)

ut.ctime = time
assert_equal(0b101, ut.flag)
assert_equal(time, ut.ctime)

ut.atime = time
assert_equal(0b111, ut.flag)
assert_equal(time, ut.atime)

ut.ctime = nil
assert_equal(0b011, ut.flag)
assert_nil ut.ctime

ut.mtime = nil
assert_equal(0b010, ut.flag)
assert_nil ut.mtime

ut.atime = nil
assert_equal(0b000, ut.flag)
assert_nil ut.atime
end

def test_pack
time = ::Zip::DOSTime.at('PS>A'.unpack1('l<'))
ut = ::Zip::ExtraField::UniversalTime.new
assert_equal("\x00", ut.pack_for_local)
assert_equal("\x00", ut.pack_for_c_dir)

ut.mtime = time
assert_equal("\x01PS>A", ut.pack_for_local)
assert_equal("\x01PS>A", ut.pack_for_c_dir)

ut.atime = time
assert_equal("\x03PS>APS>A", ut.pack_for_local)
assert_equal("\x03PS>A", ut.pack_for_c_dir)

ut.ctime = time
assert_equal("\x07PS>APS>APS>A", ut.pack_for_local)
assert_equal("\x07PS>A", ut.pack_for_c_dir)
end
end