aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYasuhiro Matsumoto <mattn.jp@gmail.com>2016-01-06 21:36:31 +0900
committerRuss Cox <rsc@golang.org>2016-10-06 19:05:52 +0000
commit4c79ed5f4483f1de065ba2b409de58ae2b6907d2 (patch)
treee4d5ef8bd3c7a4304406f935885f79e82a64a0f4
parent122abe6b122a885c52a53044eedbd3b6905b6124 (diff)
downloadgo-4c79ed5f4483f1de065ba2b409de58ae2b6907d2.tar.gz
go-4c79ed5f4483f1de065ba2b409de58ae2b6907d2.zip
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: Russ Cox <rsc@golang.org>
-rw-r--r--src/archive/zip/reader.go49
-rw-r--r--src/archive/zip/reader_test.go31
-rw-r--r--src/archive/zip/struct.go3
-rw-r--r--src/archive/zip/testdata/extra-timestamp.zipbin0 -> 152 bytes
-rw-r--r--src/archive/zip/writer.go15
-rw-r--r--src/archive/zip/writer_test.go27
6 files changed, 113 insertions, 12 deletions
diff --git a/src/archive/zip/reader.go b/src/archive/zip/reader.go
index f6c3ead3be..9bbc9a9745 100644
--- a/src/archive/zip/reader.go
+++ b/src/archive/zip/reader.go
@@ -13,6 +13,7 @@ import (
"hash/crc32"
"io"
"os"
+ "time"
)
var (
@@ -289,13 +290,16 @@ func readDirectoryHeader(f *File, r io.Reader) error {
// Other zip authors might not even follow the basic format,
// and we'll just ignore the Extra content in that case.
b := readBuf(f.Extra)
+
+ Extras:
for len(b) >= 4 { // need at least tag and size
tag := b.uint16()
size := b.uint16()
if int(size) > len(b) {
break
}
- if tag == zip64ExtraId {
+ switch tag {
+ case zip64ExtraId:
// update directory values from the zip64 extra block.
// They should only be consulted if the sizes read earlier
// are maxed out.
@@ -323,7 +327,42 @@ func readDirectoryHeader(f *File, r io.Reader) error {
}
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:]
}
@@ -508,6 +547,12 @@ func findSignatureInBlock(b []byte) int {
type readBuf []byte
+func (b *readBuf) uint8() uint8 {
+ v := uint8((*b)[0])
+ *b = (*b)[1:]
+ return v
+}
+
func (b *readBuf) uint16() uint16 {
v := binary.LittleEndian.Uint16(*b)
*b = (*b)[2:]
diff --git a/src/archive/zip/reader_test.go b/src/archive/zip/reader_test.go
index dfaae78436..576a1697a4 100644
--- a/src/archive/zip/reader_test.go
+++ b/src/archive/zip/reader_test.go
@@ -65,13 +65,13 @@ var tests = []ZipTest{
{
Name: "test.txt",
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,
},
{
Name: "gophercolor16x16.png",
File: "gophercolor16x16.png",
- Mtime: "09-05-10 15:52:58",
+ Mtime: "09-05-10 05:52:58",
Mode: 0644,
},
},
@@ -83,13 +83,13 @@ var tests = []ZipTest{
{
Name: "test.txt",
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,
},
{
Name: "gophercolor16x16.png",
File: "gophercolor16x16.png",
- Mtime: "09-05-10 15:52:58",
+ Mtime: "09-05-10 05:52:58",
Mode: 0644,
},
},
@@ -145,6 +145,17 @@ var tests = []ZipTest{
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
// descriptor signatures (which are required by OS X)
Name: "go-no-datadesc-sig.zip",
@@ -152,13 +163,13 @@ var tests = []ZipTest{
{
Name: "foo.txt",
Content: []byte("foo\n"),
- Mtime: "03-08-12 16:59:10",
+ Mtime: "03-09-12 00:59:10",
Mode: 0644,
},
{
Name: "bar.txt",
Content: []byte("bar\n"),
- Mtime: "03-08-12 16:59:12",
+ Mtime: "03-09-12 00:59:12",
Mode: 0644,
},
},
@@ -205,13 +216,13 @@ var tests = []ZipTest{
{
Name: "foo.txt",
Content: []byte("foo\n"),
- Mtime: "03-08-12 16:59:10",
+ Mtime: "03-09-12 00:59:10",
Mode: 0644,
},
{
Name: "bar.txt",
Content: []byte("bar\n"),
- Mtime: "03-08-12 16:59:12",
+ Mtime: "03-09-12 00:59:12",
Mode: 0644,
},
},
@@ -225,14 +236,14 @@ var tests = []ZipTest{
{
Name: "foo.txt",
Content: []byte("foo\n"),
- Mtime: "03-08-12 16:59:10",
+ Mtime: "03-09-12 00:59:10",
Mode: 0644,
ContentErr: ErrChecksum,
},
{
Name: "bar.txt",
Content: []byte("bar\n"),
- Mtime: "03-08-12 16:59:12",
+ Mtime: "03-09-12 00:59:12",
Mode: 0644,
},
},
diff --git a/src/archive/zip/struct.go b/src/archive/zip/struct.go
index e92d02f8a2..8e6eb840f9 100644
--- a/src/archive/zip/struct.go
+++ b/src/archive/zip/struct.go
@@ -63,6 +63,9 @@ const (
// extra header id's
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.
diff --git a/src/archive/zip/testdata/extra-timestamp.zip b/src/archive/zip/testdata/extra-timestamp.zip
new file mode 100644
index 0000000000..819e22cb68
--- /dev/null
+++ b/src/archive/zip/testdata/extra-timestamp.zip
Binary files differ
diff --git a/src/archive/zip/writer.go b/src/archive/zip/writer.go
index 3a9292e380..2a747b8f37 100644
--- a/src/archive/zip/writer.go
+++ b/src/archive/zip/writer.go
@@ -98,6 +98,16 @@ func (w *Writer) Close() error {
b.uint32(h.CompressedSize)
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.Extra)))
b.uint16(uint16(len(h.Comment)))
@@ -376,6 +386,11 @@ func (w nopCloser) Close() error {
type writeBuf []byte
+func (b *writeBuf) uint8(v uint8) {
+ (*b)[0] = v
+ *b = (*b)[1:]
+}
+
func (b *writeBuf) uint16(v uint16) {
binary.LittleEndian.PutUint16(*b, v)
*b = (*b)[2:]
diff --git a/src/archive/zip/writer_test.go b/src/archive/zip/writer_test.go
index 86841c755f..f20daa0e3d 100644
--- a/src/archive/zip/writer_test.go
+++ b/src/archive/zip/writer_test.go
@@ -11,6 +11,7 @@ import (
"math/rand"
"os"
"testing"
+ "time"
)
// TODO(adg): a more sophisticated test suite
@@ -20,6 +21,7 @@ type WriteTest struct {
Data []byte
Method uint16
Mode os.FileMode
+ Mtime string
}
var writeTests = []WriteTest{
@@ -28,30 +30,35 @@ var writeTests = []WriteTest{
Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),
Method: Store,
Mode: 0666,
+ Mtime: "02-01-08 00:01:02",
},
{
Name: "bar",
Data: nil, // large data set in the test
Method: Deflate,
Mode: 0644,
+ Mtime: "03-02-08 01:02:03",
},
{
Name: "setuid",
Data: []byte("setuid file"),
Method: Deflate,
Mode: 0755 | os.ModeSetuid,
+ Mtime: "04-03-08 02:03:04",
},
{
Name: "setgid",
Data: []byte("setgid file"),
Method: Deflate,
Mode: 0755 | os.ModeSetgid,
+ Mtime: "05-04-08 03:04:04",
},
{
Name: "symlink",
Data: []byte("../link/target"),
Method: Deflate,
Mode: 0755 | os.ModeSymlink,
+ Mtime: "03-02-08 11:22:33",
},
}
@@ -148,6 +155,11 @@ func testCreate(t *testing.T, w *Writer, wt *WriteTest) {
if wt.Mode != 0 {
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)
if err != nil {
t.Fatal(err)
@@ -178,6 +190,21 @@ func testReadFile(t *testing.T, f *File, wt *WriteTest) {
if !bytes.Equal(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) {