mirror of
https://github.com/golang/go
synced 2024-09-28 20:14:28 -06:00
archive/zip: read and write unix file modes
R=golang-dev, rsc, adg CC=golang-dev https://golang.org/cl/5124044
This commit is contained in:
parent
d53afb8d83
commit
ecda69e667
@ -238,7 +238,7 @@ func readDirectoryHeader(f *File, r io.Reader) os.Error {
|
|||||||
commentLen := int(c.Uint16(b[32:34]))
|
commentLen := int(c.Uint16(b[32:34]))
|
||||||
// startDiskNumber := c.Uint16(b[34:36]) // Unused
|
// startDiskNumber := c.Uint16(b[34:36]) // Unused
|
||||||
// internalAttributes := c.Uint16(b[36:38]) // Unused
|
// internalAttributes := c.Uint16(b[36:38]) // Unused
|
||||||
// externalAttributes := c.Uint32(b[38:42]) // Unused
|
f.ExternalAttrs = c.Uint32(b[38:42])
|
||||||
f.headerOffset = int64(c.Uint32(b[42:46]))
|
f.headerOffset = int64(c.Uint32(b[42:46]))
|
||||||
d := make([]byte, filenameLen+extraLen+commentLen)
|
d := make([]byte, filenameLen+extraLen+commentLen)
|
||||||
if _, err := io.ReadFull(r, d); err != nil {
|
if _, err := io.ReadFull(r, d); err != nil {
|
||||||
|
@ -26,6 +26,7 @@ type ZipTestFile struct {
|
|||||||
Content []byte // if blank, will attempt to compare against File
|
Content []byte // if blank, will attempt to compare against File
|
||||||
File string // name of file to compare to (relative to testdata/)
|
File string // name of file to compare to (relative to testdata/)
|
||||||
Mtime string // modified time in format "mm-dd-yy hh:mm:ss"
|
Mtime string // modified time in format "mm-dd-yy hh:mm:ss"
|
||||||
|
Mode uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caution: The Mtime values found for the test files should correspond to
|
// Caution: The Mtime values found for the test files should correspond to
|
||||||
@ -47,11 +48,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 12:12:02",
|
||||||
|
Mode: 0x81a4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "gophercolor16x16.png",
|
Name: "gophercolor16x16.png",
|
||||||
File: "gophercolor16x16.png",
|
File: "gophercolor16x16.png",
|
||||||
Mtime: "09-05-10 15:52:58",
|
Mtime: "09-05-10 15:52:58",
|
||||||
|
Mode: 0x81a4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -162,6 +165,8 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
|
|||||||
t.Errorf("%s: mtime=%s (%d); want %s (%d)", f.Name, time.SecondsToUTC(got), got, mtime, want)
|
t.Errorf("%s: mtime=%s (%d); want %s (%d)", f.Name, time.SecondsToUTC(got), got, mtime, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testFileMode(t, f, ft.Mode)
|
||||||
|
|
||||||
size0 := f.UncompressedSize
|
size0 := f.UncompressedSize
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
@ -203,6 +208,19 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testFileMode(t *testing.T, f *File, want uint32) {
|
||||||
|
mode, err := f.Mode()
|
||||||
|
if want == 0 {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s mode: got %v, want none", f.Name, mode)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
t.Errorf("%s mode: %s", f.Name, err)
|
||||||
|
} else if mode != want {
|
||||||
|
t.Errorf("%s mode: want 0x%x, got 0x%x", f.Name, want, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalidFiles(t *testing.T) {
|
func TestInvalidFiles(t *testing.T) {
|
||||||
const size = 1024 * 70 // 70kb
|
const size = 1024 * 70 // 70kb
|
||||||
b := make([]byte, size)
|
b := make([]byte, size)
|
||||||
|
@ -28,6 +28,9 @@ const (
|
|||||||
directoryHeaderLen = 46 // + filename + extra + comment
|
directoryHeaderLen = 46 // + filename + extra + comment
|
||||||
directoryEndLen = 22 // + comment
|
directoryEndLen = 22 // + comment
|
||||||
dataDescriptorLen = 12
|
dataDescriptorLen = 12
|
||||||
|
|
||||||
|
// Constants for the first byte in CreatorVersion
|
||||||
|
creatorUnix = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileHeader struct {
|
type FileHeader struct {
|
||||||
@ -42,6 +45,7 @@ type FileHeader struct {
|
|||||||
CompressedSize uint32
|
CompressedSize uint32
|
||||||
UncompressedSize uint32
|
UncompressedSize uint32
|
||||||
Extra []byte
|
Extra []byte
|
||||||
|
ExternalAttrs uint32 // Meaning depends on CreatorVersion
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,3 +93,18 @@ func (h *FileHeader) Mtime_ns() int64 {
|
|||||||
t := msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
|
t := msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
|
||||||
return t.Seconds() * 1e9
|
return t.Seconds() * 1e9
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mode returns the permission and mode bits for the FileHeader.
|
||||||
|
// An error is returned in case the information is not available.
|
||||||
|
func (h *FileHeader) Mode() (mode uint32, err os.Error) {
|
||||||
|
if h.CreatorVersion>>8 == creatorUnix {
|
||||||
|
return h.ExternalAttrs >> 16, nil
|
||||||
|
}
|
||||||
|
return 0, os.NewError("file mode not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMode changes the permission and mode bits for the FileHeader.
|
||||||
|
func (h *FileHeader) SetMode(mode uint32) {
|
||||||
|
h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
|
||||||
|
h.ExternalAttrs = mode << 16
|
||||||
|
}
|
||||||
|
@ -69,7 +69,7 @@ func (w *Writer) Close() (err os.Error) {
|
|||||||
write(w, uint16(len(h.Comment)))
|
write(w, uint16(len(h.Comment)))
|
||||||
write(w, uint16(0)) // disk number start
|
write(w, uint16(0)) // disk number start
|
||||||
write(w, uint16(0)) // internal file attributes
|
write(w, uint16(0)) // internal file attributes
|
||||||
write(w, uint32(0)) // external file attributes
|
write(w, h.ExternalAttrs)
|
||||||
write(w, h.offset)
|
write(w, h.offset)
|
||||||
writeBytes(w, []byte(h.Name))
|
writeBytes(w, []byte(h.Name))
|
||||||
writeBytes(w, h.Extra)
|
writeBytes(w, h.Extra)
|
||||||
@ -115,7 +115,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, os.Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fh.Flags |= 0x8 // we will write a data descriptor
|
fh.Flags |= 0x8 // we will write a data descriptor
|
||||||
fh.CreatorVersion = 0x14
|
fh.CreatorVersion = fh.CreatorVersion&0xff00 | 0x14
|
||||||
fh.ReaderVersion = 0x14
|
fh.ReaderVersion = 0x14
|
||||||
|
|
||||||
fw := &fileWriter{
|
fw := &fileWriter{
|
||||||
|
@ -13,19 +13,45 @@ import (
|
|||||||
|
|
||||||
// TODO(adg): a more sophisticated test suite
|
// TODO(adg): a more sophisticated test suite
|
||||||
|
|
||||||
const testString = "Rabbits, guinea pigs, gophers, marsupial rats, and quolls."
|
type WriteTest struct {
|
||||||
|
Name string
|
||||||
|
Data []byte
|
||||||
|
Method uint16
|
||||||
|
Mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var writeTests = []WriteTest{
|
||||||
|
WriteTest{
|
||||||
|
Name: "foo",
|
||||||
|
Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),
|
||||||
|
Method: Store,
|
||||||
|
},
|
||||||
|
WriteTest{
|
||||||
|
Name: "bar",
|
||||||
|
Data: nil, // large data set in the test
|
||||||
|
Method: Deflate,
|
||||||
|
Mode: 0x81ed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriter(t *testing.T) {
|
func TestWriter(t *testing.T) {
|
||||||
largeData := make([]byte, 1<<17)
|
largeData := make([]byte, 1<<17)
|
||||||
for i := range largeData {
|
for i := range largeData {
|
||||||
largeData[i] = byte(rand.Int())
|
largeData[i] = byte(rand.Int())
|
||||||
}
|
}
|
||||||
|
writeTests[1].Data = largeData
|
||||||
|
defer func() {
|
||||||
|
writeTests[1].Data = nil
|
||||||
|
}()
|
||||||
|
|
||||||
// write a zip file
|
// write a zip file
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
w := NewWriter(buf)
|
w := NewWriter(buf)
|
||||||
testCreate(t, w, "foo", []byte(testString), Store)
|
|
||||||
testCreate(t, w, "bar", largeData, Deflate)
|
for _, wt := range writeTests {
|
||||||
|
testCreate(t, w, &wt)
|
||||||
|
}
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -35,26 +61,34 @@ func TestWriter(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testReadFile(t, r.File[0], []byte(testString))
|
for i, wt := range writeTests {
|
||||||
testReadFile(t, r.File[1], largeData)
|
testReadFile(t, r.File[i], &wt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCreate(t *testing.T, w *Writer, name string, data []byte, method uint16) {
|
func testCreate(t *testing.T, w *Writer, wt *WriteTest) {
|
||||||
header := &FileHeader{
|
header := &FileHeader{
|
||||||
Name: name,
|
Name: wt.Name,
|
||||||
Method: method,
|
Method: wt.Method,
|
||||||
|
}
|
||||||
|
if wt.Mode != 0 {
|
||||||
|
header.SetMode(wt.Mode)
|
||||||
}
|
}
|
||||||
f, err := w.CreateHeader(header)
|
f, err := w.CreateHeader(header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = f.Write(data)
|
_, err = f.Write(wt.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testReadFile(t *testing.T, f *File, data []byte) {
|
func testReadFile(t *testing.T, f *File, wt *WriteTest) {
|
||||||
|
if f.Name != wt.Name {
|
||||||
|
t.Fatal("File name: got %q, want %q", f.Name, wt.Name)
|
||||||
|
}
|
||||||
|
testFileMode(t, f, wt.Mode)
|
||||||
rc, err := f.Open()
|
rc, err := f.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("opening:", err)
|
t.Fatal("opening:", err)
|
||||||
@ -67,7 +101,7 @@ func testReadFile(t *testing.T, f *File, data []byte) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("closing:", err)
|
t.Fatal("closing:", err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(b, data) {
|
if !bytes.Equal(b, wt.Data) {
|
||||||
t.Errorf("File contents %q, want %q", b, data)
|
t.Errorf("File contents %q, want %q", b, wt.Data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user