]> Cypherpunks repositories - gostls13.git/commitdiff
archive/zip: handle mtime in NTFS/UNIX/ExtendedTS extra fields
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Wed, 6 Jan 2016 12:36:31 +0000 (21:36 +0900)
committerRuss Cox <rsc@golang.org>
Thu, 6 Oct 2016 19:05:52 +0000 (19:05 +0000)
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>
src/archive/zip/reader.go
src/archive/zip/reader_test.go
src/archive/zip/struct.go
src/archive/zip/testdata/extra-timestamp.zip [new file with mode: 0644]
src/archive/zip/writer.go
src/archive/zip/writer_test.go

index f6c3ead3beabd20aca92de87c2b9ab2fba664556..9bbc9a97454302656331fefdc80224ea45d6de6a 100644 (file)
@@ -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:]
index dfaae784361b7184ffdc36e89bd955cae0bd7233..576a1697a444959376f46b927ebf82023b059687 100644 (file)
@@ -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,
                        },
                },
@@ -144,6 +144,17 @@ var tests = []ZipTest{
                Name: "unix.zip",
                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)
@@ -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,
                        },
                },
index e92d02f8a2e872ddab84503a6ea0cd4e238faa78..8e6eb840f949fe7a757f82bbcb1eed805fa8b4fd 100644 (file)
@@ -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 (file)
index 0000000..819e22c
Binary files /dev/null and b/src/archive/zip/testdata/extra-timestamp.zip differ
index 3a9292e380705573fdd0249602cb5ab83177b42e..2a747b8f37e542ad3d6439fb190832efae09f62d 100644 (file)
@@ -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:]
index 86841c755f160ee371976556d03b1506b7c271e2..f20daa0e3d3fa5de8da311af178ab787a44771ed 100644 (file)
@@ -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) {