Commit 4c79ed5f authored by Yasuhiro Matsumoto's avatar Yasuhiro Matsumoto Committed by Russ Cox

archive/zip: handle mtime in NTFS/UNIX/ExtendedTS extra fields

Handle NTFS timestamp, UNIX timestamp, Extended extra timestamp.
Writer supports only Extended extra timestamp field, matching most
zip creators.

Fixes #10242.

Change-Id: Id665db274e63def98659231391fb77392267ac1e
Reviewed-on: https://go-review.googlesource.com/18274
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 122abe6b
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"hash/crc32" "hash/crc32"
"io" "io"
"os" "os"
"time"
) )
var ( var (
...@@ -289,13 +290,16 @@ func readDirectoryHeader(f *File, r io.Reader) error { ...@@ -289,13 +290,16 @@ func readDirectoryHeader(f *File, r io.Reader) error {
// Other zip authors might not even follow the basic format, // Other zip authors might not even follow the basic format,
// and we'll just ignore the Extra content in that case. // and we'll just ignore the Extra content in that case.
b := readBuf(f.Extra) b := readBuf(f.Extra)
Extras:
for len(b) >= 4 { // need at least tag and size for len(b) >= 4 { // need at least tag and size
tag := b.uint16() tag := b.uint16()
size := b.uint16() size := b.uint16()
if int(size) > len(b) { if int(size) > len(b) {
break break
} }
if tag == zip64ExtraId { switch tag {
case zip64ExtraId:
// update directory values from the zip64 extra block. // update directory values from the zip64 extra block.
// They should only be consulted if the sizes read earlier // They should only be consulted if the sizes read earlier
// are maxed out. // are maxed out.
...@@ -323,7 +327,42 @@ func readDirectoryHeader(f *File, r io.Reader) error { ...@@ -323,7 +327,42 @@ func readDirectoryHeader(f *File, r io.Reader) error {
} }
f.headerOffset = int64(eb.uint64()) f.headerOffset = int64(eb.uint64())
} }
break break Extras
case ntfsExtraId:
if size == 32 {
eb := readBuf(b[:size])
eb.uint32() // reserved
eb.uint16() // tag1
size1 := eb.uint16()
if size1 == 24 {
sub := readBuf(eb[:size1])
lo := sub.uint32()
hi := sub.uint32()
tick := (uint64(uint64(lo)|uint64(hi)<<32) - 116444736000000000) / 10000000
f.SetModTime(time.Unix(int64(tick), 0))
}
}
break Extras
case unixExtraId:
if size >= 12 {
eb := readBuf(b[:size])
eb.uint32() // AcTime
epoch := eb.uint32() // ModTime
f.SetModTime(time.Unix(int64(epoch), 0))
break Extras
}
case exttsExtraId:
if size >= 3 {
eb := readBuf(b[:size])
flags := eb.uint8() // Flags
epoch := eb.uint32() // AcTime/ModTime/CrTime
if flags&1 != 0 {
f.SetModTime(time.Unix(int64(epoch), 0))
}
break Extras
}
} }
b = b[size:] b = b[size:]
} }
...@@ -508,6 +547,12 @@ func findSignatureInBlock(b []byte) int { ...@@ -508,6 +547,12 @@ func findSignatureInBlock(b []byte) int {
type readBuf []byte type readBuf []byte
func (b *readBuf) uint8() uint8 {
v := uint8((*b)[0])
*b = (*b)[1:]
return v
}
func (b *readBuf) uint16() uint16 { func (b *readBuf) uint16() uint16 {
v := binary.LittleEndian.Uint16(*b) v := binary.LittleEndian.Uint16(*b)
*b = (*b)[2:] *b = (*b)[2:]
......
...@@ -65,13 +65,13 @@ var tests = []ZipTest{ ...@@ -65,13 +65,13 @@ var tests = []ZipTest{
{ {
Name: "test.txt", Name: "test.txt",
Content: []byte("This is a test text file.\n"), Content: []byte("This is a test text file.\n"),
Mtime: "09-05-10 12:12:02", Mtime: "09-05-10 02:12:00",
Mode: 0644, Mode: 0644,
}, },
{ {
Name: "gophercolor16x16.png", Name: "gophercolor16x16.png",
File: "gophercolor16x16.png", File: "gophercolor16x16.png",
Mtime: "09-05-10 15:52:58", Mtime: "09-05-10 05:52:58",
Mode: 0644, Mode: 0644,
}, },
}, },
...@@ -83,13 +83,13 @@ var tests = []ZipTest{ ...@@ -83,13 +83,13 @@ var tests = []ZipTest{
{ {
Name: "test.txt", Name: "test.txt",
Content: []byte("This is a test text file.\n"), Content: []byte("This is a test text file.\n"),
Mtime: "09-05-10 12:12:02", Mtime: "09-05-10 02:12:00",
Mode: 0644, Mode: 0644,
}, },
{ {
Name: "gophercolor16x16.png", Name: "gophercolor16x16.png",
File: "gophercolor16x16.png", File: "gophercolor16x16.png",
Mtime: "09-05-10 15:52:58", Mtime: "09-05-10 05:52:58",
Mode: 0644, Mode: 0644,
}, },
}, },
...@@ -144,6 +144,17 @@ var tests = []ZipTest{ ...@@ -144,6 +144,17 @@ var tests = []ZipTest{
Name: "unix.zip", Name: "unix.zip",
File: crossPlatform, File: crossPlatform,
}, },
{
Name: "extra-timestamp.zip",
File: []ZipTestFile{
{
Name: "hello.txt",
Content: []byte(""),
Mtime: "01-06-16 12:25:56",
Mode: 0666,
},
},
},
{ {
// created by Go, before we wrote the "optional" data // created by Go, before we wrote the "optional" data
// descriptor signatures (which are required by OS X) // descriptor signatures (which are required by OS X)
...@@ -152,13 +163,13 @@ var tests = []ZipTest{ ...@@ -152,13 +163,13 @@ var tests = []ZipTest{
{ {
Name: "foo.txt", Name: "foo.txt",
Content: []byte("foo\n"), Content: []byte("foo\n"),
Mtime: "03-08-12 16:59:10", Mtime: "03-09-12 00:59:10",
Mode: 0644, Mode: 0644,
}, },
{ {
Name: "bar.txt", Name: "bar.txt",
Content: []byte("bar\n"), Content: []byte("bar\n"),
Mtime: "03-08-12 16:59:12", Mtime: "03-09-12 00:59:12",
Mode: 0644, Mode: 0644,
}, },
}, },
...@@ -205,13 +216,13 @@ var tests = []ZipTest{ ...@@ -205,13 +216,13 @@ var tests = []ZipTest{
{ {
Name: "foo.txt", Name: "foo.txt",
Content: []byte("foo\n"), Content: []byte("foo\n"),
Mtime: "03-08-12 16:59:10", Mtime: "03-09-12 00:59:10",
Mode: 0644, Mode: 0644,
}, },
{ {
Name: "bar.txt", Name: "bar.txt",
Content: []byte("bar\n"), Content: []byte("bar\n"),
Mtime: "03-08-12 16:59:12", Mtime: "03-09-12 00:59:12",
Mode: 0644, Mode: 0644,
}, },
}, },
...@@ -225,14 +236,14 @@ var tests = []ZipTest{ ...@@ -225,14 +236,14 @@ var tests = []ZipTest{
{ {
Name: "foo.txt", Name: "foo.txt",
Content: []byte("foo\n"), Content: []byte("foo\n"),
Mtime: "03-08-12 16:59:10", Mtime: "03-09-12 00:59:10",
Mode: 0644, Mode: 0644,
ContentErr: ErrChecksum, ContentErr: ErrChecksum,
}, },
{ {
Name: "bar.txt", Name: "bar.txt",
Content: []byte("bar\n"), Content: []byte("bar\n"),
Mtime: "03-08-12 16:59:12", Mtime: "03-09-12 00:59:12",
Mode: 0644, Mode: 0644,
}, },
}, },
......
...@@ -63,6 +63,9 @@ const ( ...@@ -63,6 +63,9 @@ const (
// extra header id's // extra header id's
zip64ExtraId = 0x0001 // zip64 Extended Information Extra Field zip64ExtraId = 0x0001 // zip64 Extended Information Extra Field
ntfsExtraId = 0x000a // NTFS Extra Field
unixExtraId = 0x000d // UNIX Extra Field
exttsExtraId = 0x5455 // Extra Timestamp Extra Field
) )
// FileHeader describes a file within a zip file. // FileHeader describes a file within a zip file.
......
...@@ -98,6 +98,16 @@ func (w *Writer) Close() error { ...@@ -98,6 +98,16 @@ func (w *Writer) Close() error {
b.uint32(h.CompressedSize) b.uint32(h.CompressedSize)
b.uint32(h.UncompressedSize) b.uint32(h.UncompressedSize)
} }
mt := uint32(h.FileHeader.ModTime().Unix())
var mbuf [9]byte // 2x uint16 + uint8 + uint32
eb := writeBuf(mbuf[:])
eb.uint16(exttsExtraId)
eb.uint16(5) // size = uint8 + uint32
eb.uint8(1) // flags = modtime
eb.uint32(mt) // ModTime
h.Extra = append(h.Extra, mbuf[:]...)
b.uint16(uint16(len(h.Name))) b.uint16(uint16(len(h.Name)))
b.uint16(uint16(len(h.Extra))) b.uint16(uint16(len(h.Extra)))
b.uint16(uint16(len(h.Comment))) b.uint16(uint16(len(h.Comment)))
...@@ -376,6 +386,11 @@ func (w nopCloser) Close() error { ...@@ -376,6 +386,11 @@ func (w nopCloser) Close() error {
type writeBuf []byte type writeBuf []byte
func (b *writeBuf) uint8(v uint8) {
(*b)[0] = v
*b = (*b)[1:]
}
func (b *writeBuf) uint16(v uint16) { func (b *writeBuf) uint16(v uint16) {
binary.LittleEndian.PutUint16(*b, v) binary.LittleEndian.PutUint16(*b, v)
*b = (*b)[2:] *b = (*b)[2:]
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"testing" "testing"
"time"
) )
// TODO(adg): a more sophisticated test suite // TODO(adg): a more sophisticated test suite
...@@ -20,6 +21,7 @@ type WriteTest struct { ...@@ -20,6 +21,7 @@ type WriteTest struct {
Data []byte Data []byte
Method uint16 Method uint16
Mode os.FileMode Mode os.FileMode
Mtime string
} }
var writeTests = []WriteTest{ var writeTests = []WriteTest{
...@@ -28,30 +30,35 @@ var writeTests = []WriteTest{ ...@@ -28,30 +30,35 @@ var writeTests = []WriteTest{
Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."), Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),
Method: Store, Method: Store,
Mode: 0666, Mode: 0666,
Mtime: "02-01-08 00:01:02",
}, },
{ {
Name: "bar", Name: "bar",
Data: nil, // large data set in the test Data: nil, // large data set in the test
Method: Deflate, Method: Deflate,
Mode: 0644, Mode: 0644,
Mtime: "03-02-08 01:02:03",
}, },
{ {
Name: "setuid", Name: "setuid",
Data: []byte("setuid file"), Data: []byte("setuid file"),
Method: Deflate, Method: Deflate,
Mode: 0755 | os.ModeSetuid, Mode: 0755 | os.ModeSetuid,
Mtime: "04-03-08 02:03:04",
}, },
{ {
Name: "setgid", Name: "setgid",
Data: []byte("setgid file"), Data: []byte("setgid file"),
Method: Deflate, Method: Deflate,
Mode: 0755 | os.ModeSetgid, Mode: 0755 | os.ModeSetgid,
Mtime: "05-04-08 03:04:04",
}, },
{ {
Name: "symlink", Name: "symlink",
Data: []byte("../link/target"), Data: []byte("../link/target"),
Method: Deflate, Method: Deflate,
Mode: 0755 | os.ModeSymlink, Mode: 0755 | os.ModeSymlink,
Mtime: "03-02-08 11:22:33",
}, },
} }
...@@ -148,6 +155,11 @@ func testCreate(t *testing.T, w *Writer, wt *WriteTest) { ...@@ -148,6 +155,11 @@ func testCreate(t *testing.T, w *Writer, wt *WriteTest) {
if wt.Mode != 0 { if wt.Mode != 0 {
header.SetMode(wt.Mode) header.SetMode(wt.Mode)
} }
mtime, err := time.Parse("01-02-06 15:04:05", wt.Mtime)
if err != nil {
t.Fatal("time.Parse:", err)
}
header.SetModTime(mtime)
f, err := w.CreateHeader(header) f, err := w.CreateHeader(header)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -178,6 +190,21 @@ func testReadFile(t *testing.T, f *File, wt *WriteTest) { ...@@ -178,6 +190,21 @@ func testReadFile(t *testing.T, f *File, wt *WriteTest) {
if !bytes.Equal(b, wt.Data) { if !bytes.Equal(b, wt.Data) {
t.Errorf("File contents %q, want %q", b, wt.Data) t.Errorf("File contents %q, want %q", b, wt.Data)
} }
mtime, err := time.Parse("01-02-06 15:04:05", wt.Mtime)
if err != nil {
t.Fatal("time.Parse:", err)
}
diff := mtime.Sub(f.ModTime())
if diff < 0 {
diff = -diff
}
// allow several time span
if diff > 5*time.Second {
t.Errorf("File modtime %v, want %v", mtime, f.ModTime())
}
} }
func BenchmarkCompressedZipGarbage(b *testing.B) { func BenchmarkCompressedZipGarbage(b *testing.B) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment