summaryrefslogtreecommitdiff
path: root/src/pkg/archive
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/archive')
-rw-r--r--src/pkg/archive/tar/common.go53
-rw-r--r--src/pkg/archive/tar/reader.go36
-rw-r--r--src/pkg/archive/tar/reader_test.go109
-rw-r--r--src/pkg/archive/tar/writer.go48
-rw-r--r--src/pkg/archive/tar/writer_test.go21
-rw-r--r--src/pkg/archive/zip/reader.go60
-rw-r--r--src/pkg/archive/zip/reader_test.go97
-rw-r--r--src/pkg/archive/zip/struct.go177
-rw-r--r--src/pkg/archive/zip/testdata/unix.zipbin0 -> 620 bytes
-rw-r--r--src/pkg/archive/zip/testdata/winxp.zipbin0 -> 412 bytes
-rw-r--r--src/pkg/archive/zip/writer.go30
-rw-r--r--src/pkg/archive/zip/writer_test.go80
-rw-r--r--src/pkg/archive/zip/zip_test.go37
13 files changed, 513 insertions, 235 deletions
diff --git a/src/pkg/archive/tar/common.go b/src/pkg/archive/tar/common.go
index 67355086a..fc7a40923 100644
--- a/src/pkg/archive/tar/common.go
+++ b/src/pkg/archive/tar/common.go
@@ -11,41 +11,42 @@
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
package tar
+import "time"
+
const (
blockSize = 512
// Types
- TypeReg = '0' // regular file.
- TypeRegA = '\x00' // regular file.
- TypeLink = '1' // hard link.
- TypeSymlink = '2' // symbolic link.
- TypeChar = '3' // character device node.
- TypeBlock = '4' // block device node.
- TypeDir = '5' // directory.
- TypeFifo = '6' // fifo node.
- TypeCont = '7' // reserved.
- TypeXHeader = 'x' // extended header.
- TypeXGlobalHeader = 'g' // global extended header.
+ TypeReg = '0' // regular file
+ TypeRegA = '\x00' // regular file
+ TypeLink = '1' // hard link
+ TypeSymlink = '2' // symbolic link
+ TypeChar = '3' // character device node
+ TypeBlock = '4' // block device node
+ TypeDir = '5' // directory
+ TypeFifo = '6' // fifo node
+ TypeCont = '7' // reserved
+ TypeXHeader = 'x' // extended header
+ TypeXGlobalHeader = 'g' // global extended header
)
// A Header represents a single header in a tar archive.
// Some fields may not be populated.
type Header struct {
- Name string // name of header file entry.
- Mode int64 // permission and mode bits.
- Uid int // user id of owner.
- Gid int // group id of owner.
- Size int64 // length in bytes.
- Mtime int64 // modified time; seconds since epoch.
- Typeflag byte // type of header entry.
- Linkname string // target name of link.
- Uname string // user name of owner.
- Gname string // group name of owner.
- Devmajor int64 // major number of character or block device.
- Devminor int64 // minor number of character or block device.
- Atime int64 // access time; seconds since epoch.
- Ctime int64 // status change time; seconds since epoch.
-
+ Name string // name of header file entry
+ Mode int64 // permission and mode bits
+ Uid int // user id of owner
+ Gid int // group id of owner
+ Size int64 // length in bytes
+ ModTime time.Time // modified time
+ Typeflag byte // type of header entry
+ Linkname string // target name of link
+ Uname string // user name of owner
+ Gname string // group name of owner
+ Devmajor int64 // major number of character or block device
+ Devminor int64 // minor number of character or block device
+ AccessTime time.Time // access time
+ ChangeTime time.Time // status change time
}
var zeroBlock = make([]byte, blockSize)
diff --git a/src/pkg/archive/tar/reader.go b/src/pkg/archive/tar/reader.go
index 45d95c3df..755a730c8 100644
--- a/src/pkg/archive/tar/reader.go
+++ b/src/pkg/archive/tar/reader.go
@@ -9,14 +9,16 @@ package tar
import (
"bytes"
+ "errors"
"io"
"io/ioutil"
"os"
"strconv"
+ "time"
)
var (
- HeaderError = os.NewError("invalid tar header")
+ ErrHeader = errors.New("invalid tar header")
)
// A Reader provides sequential access to the contents of a tar archive.
@@ -28,7 +30,7 @@ var (
// tr := tar.NewReader(r)
// for {
// hdr, err := tr.Next()
-// if err == os.EOF {
+// if err == io.EOF {
// // end of tar archive
// break
// }
@@ -39,7 +41,7 @@ var (
// }
type Reader struct {
r io.Reader
- err os.Error
+ err error
nb int64 // number of unread bytes for current file entry
pad int64 // amount of padding (ignored) after current file entry
}
@@ -48,7 +50,7 @@ type Reader struct {
func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
// Next advances to the next entry in the tar archive.
-func (tr *Reader) Next() (*Header, os.Error) {
+func (tr *Reader) Next() (*Header, error) {
var hdr *Header
if tr.err == nil {
tr.skipUnread()
@@ -78,7 +80,7 @@ func (tr *Reader) octal(b []byte) int64 {
for len(b) > 0 && (b[len(b)-1] == ' ' || b[len(b)-1] == '\x00') {
b = b[0 : len(b)-1]
}
- x, err := strconv.Btoui64(cString(b), 8)
+ x, err := strconv.ParseUint(cString(b), 8, 64)
if err != nil {
tr.err = err
}
@@ -94,7 +96,7 @@ func (tr *Reader) skipUnread() {
return
}
}
- _, tr.err = io.Copyn(ioutil.Discard, tr.r, nr)
+ _, tr.err = io.CopyN(ioutil.Discard, tr.r, nr)
}
func (tr *Reader) verifyChecksum(header []byte) bool {
@@ -119,15 +121,15 @@ func (tr *Reader) readHeader() *Header {
return nil
}
if bytes.Equal(header, zeroBlock[0:blockSize]) {
- tr.err = os.EOF
+ tr.err = io.EOF
} else {
- tr.err = HeaderError // zero block and then non-zero block
+ tr.err = ErrHeader // zero block and then non-zero block
}
return nil
}
if !tr.verifyChecksum(header) {
- tr.err = HeaderError
+ tr.err = ErrHeader
return nil
}
@@ -140,7 +142,7 @@ func (tr *Reader) readHeader() *Header {
hdr.Uid = int(tr.octal(s.next(8)))
hdr.Gid = int(tr.octal(s.next(8)))
hdr.Size = tr.octal(s.next(12))
- hdr.Mtime = tr.octal(s.next(12))
+ hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0)
s.next(8) // chksum
hdr.Typeflag = s.next(1)[0]
hdr.Linkname = cString(s.next(100))
@@ -177,8 +179,8 @@ func (tr *Reader) readHeader() *Header {
prefix = cString(s.next(155))
case "star":
prefix = cString(s.next(131))
- hdr.Atime = tr.octal(s.next(12))
- hdr.Ctime = tr.octal(s.next(12))
+ hdr.AccessTime = time.Unix(tr.octal(s.next(12)), 0)
+ hdr.ChangeTime = time.Unix(tr.octal(s.next(12)), 0)
}
if len(prefix) > 0 {
hdr.Name = prefix + "/" + hdr.Name
@@ -186,7 +188,7 @@ func (tr *Reader) readHeader() *Header {
}
if tr.err != nil {
- tr.err = HeaderError
+ tr.err = ErrHeader
return nil
}
@@ -199,12 +201,12 @@ func (tr *Reader) readHeader() *Header {
}
// Read reads from the current entry in the tar archive.
-// It returns 0, os.EOF when it reaches the end of that entry,
+// It returns 0, io.EOF when it reaches the end of that entry,
// until Next is called to advance to the next entry.
-func (tr *Reader) Read(b []byte) (n int, err os.Error) {
+func (tr *Reader) Read(b []byte) (n int, err error) {
if tr.nb == 0 {
// file consumed
- return 0, os.EOF
+ return 0, io.EOF
}
if int64(len(b)) > tr.nb {
@@ -213,7 +215,7 @@ func (tr *Reader) Read(b []byte) (n int, err os.Error) {
n, err = tr.r.Read(b)
tr.nb -= int64(n)
- if err == os.EOF && tr.nb > 0 {
+ if err == io.EOF && tr.nb > 0 {
err = io.ErrUnexpectedEOF
}
tr.err = err
diff --git a/src/pkg/archive/tar/reader_test.go b/src/pkg/archive/tar/reader_test.go
index f473c900f..0a8646c39 100644
--- a/src/pkg/archive/tar/reader_test.go
+++ b/src/pkg/archive/tar/reader_test.go
@@ -10,8 +10,8 @@ import (
"fmt"
"io"
"os"
- "reflect"
"testing"
+ "time"
)
type untarTest struct {
@@ -23,24 +23,24 @@ type untarTest struct {
var gnuTarTest = &untarTest{
file: "testdata/gnu.tar",
headers: []*Header{
- &Header{
+ {
Name: "small.txt",
Mode: 0640,
Uid: 73025,
Gid: 5000,
Size: 5,
- Mtime: 1244428340,
+ ModTime: time.Unix(1244428340, 0),
Typeflag: '0',
Uname: "dsymonds",
Gname: "eng",
},
- &Header{
+ {
Name: "small2.txt",
Mode: 0640,
Uid: 73025,
Gid: 5000,
Size: 11,
- Mtime: 1244436044,
+ ModTime: time.Unix(1244436044, 0),
Typeflag: '0',
Uname: "dsymonds",
Gname: "eng",
@@ -54,56 +54,56 @@ var gnuTarTest = &untarTest{
var untarTests = []*untarTest{
gnuTarTest,
- &untarTest{
+ {
file: "testdata/star.tar",
headers: []*Header{
- &Header{
- Name: "small.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 5,
- Mtime: 1244592783,
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- Atime: 1244592783,
- Ctime: 1244592783,
+ {
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ ModTime: time.Unix(1244592783, 0),
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ AccessTime: time.Unix(1244592783, 0),
+ ChangeTime: time.Unix(1244592783, 0),
},
- &Header{
- Name: "small2.txt",
- Mode: 0640,
- Uid: 73025,
- Gid: 5000,
- Size: 11,
- Mtime: 1244592783,
- Typeflag: '0',
- Uname: "dsymonds",
- Gname: "eng",
- Atime: 1244592783,
- Ctime: 1244592783,
+ {
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ ModTime: time.Unix(1244592783, 0),
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ AccessTime: time.Unix(1244592783, 0),
+ ChangeTime: time.Unix(1244592783, 0),
},
},
},
- &untarTest{
+ {
file: "testdata/v7.tar",
headers: []*Header{
- &Header{
+ {
Name: "small.txt",
Mode: 0444,
Uid: 73025,
Gid: 5000,
Size: 5,
- Mtime: 1244593104,
+ ModTime: time.Unix(1244593104, 0),
Typeflag: '\x00',
},
- &Header{
+ {
Name: "small2.txt",
Mode: 0444,
Uid: 73025,
Gid: 5000,
Size: 11,
- Mtime: 1244593104,
+ ModTime: time.Unix(1244593104, 0),
Typeflag: '\x00',
},
},
@@ -126,13 +126,13 @@ testLoop:
f.Close()
continue testLoop
}
- if !reflect.DeepEqual(hdr, header) {
+ if *hdr != *header {
t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
i, j, *hdr, *header)
}
}
hdr, err := tr.Next()
- if err == os.EOF {
+ if err == io.EOF {
break
}
if hdr != nil || err != nil {
@@ -195,12 +195,12 @@ func TestIncrementalRead(t *testing.T) {
// loop over all files
for ; ; nread++ {
hdr, err := tr.Next()
- if hdr == nil || err == os.EOF {
+ if hdr == nil || err == io.EOF {
break
}
// check the header
- if !reflect.DeepEqual(hdr, headers[nread]) {
+ if *hdr != *headers[nread] {
t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
*hdr, headers[nread])
}
@@ -211,7 +211,7 @@ func TestIncrementalRead(t *testing.T) {
rdbuf := make([]uint8, 8)
for {
nr, err := tr.Read(rdbuf)
- if err == os.EOF {
+ if err == io.EOF {
break
}
if err != nil {
@@ -221,7 +221,7 @@ func TestIncrementalRead(t *testing.T) {
h.Write(rdbuf[0:nr])
}
// verify checksum
- have := fmt.Sprintf("%x", h.Sum())
+ have := fmt.Sprintf("%x", h.Sum(nil))
want := cksums[nread]
if want != have {
t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want)
@@ -240,31 +240,20 @@ func TestNonSeekable(t *testing.T) {
}
defer f.Close()
- // pipe the data in
- r, w, err := os.Pipe()
- if err != nil {
- t.Fatalf("Unexpected error %s", err)
+ type readerOnly struct {
+ io.Reader
}
- go func() {
- rdbuf := make([]uint8, 1<<16)
- for {
- nr, err := f.Read(rdbuf)
- w.Write(rdbuf[0:nr])
- if err == os.EOF {
- break
- }
- }
- w.Close()
- }()
-
- tr := NewReader(r)
+ tr := NewReader(readerOnly{f})
nread := 0
for ; ; nread++ {
- hdr, err := tr.Next()
- if hdr == nil || err == os.EOF {
+ _, err := tr.Next()
+ if err == io.EOF {
break
}
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
}
if nread != len(test.headers) {
diff --git a/src/pkg/archive/tar/writer.go b/src/pkg/archive/tar/writer.go
index c6ce2241a..d35726bf9 100644
--- a/src/pkg/archive/tar/writer.go
+++ b/src/pkg/archive/tar/writer.go
@@ -8,15 +8,15 @@ package tar
// - catch more errors (no first header, write after close, etc.)
import (
+ "errors"
"io"
- "os"
"strconv"
)
var (
- ErrWriteTooLong = os.NewError("write too long")
- ErrFieldTooLong = os.NewError("header field too long")
- ErrWriteAfterClose = os.NewError("write after close")
+ ErrWriteTooLong = errors.New("write too long")
+ ErrFieldTooLong = errors.New("header field too long")
+ ErrWriteAfterClose = errors.New("write after close")
)
// A Writer provides sequential writing of a tar archive in POSIX.1 format.
@@ -36,7 +36,7 @@ var (
// tw.Close()
type Writer struct {
w io.Writer
- err os.Error
+ err error
nb int64 // number of unwritten bytes for current file entry
pad int64 // amount of padding to write after current file entry
closed bool
@@ -47,7 +47,7 @@ type Writer struct {
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
// Flush finishes writing the current file (optional).
-func (tw *Writer) Flush() os.Error {
+func (tw *Writer) Flush() error {
n := tw.nb + tw.pad
for n > 0 && tw.err == nil {
nr := n
@@ -79,7 +79,7 @@ func (tw *Writer) cString(b []byte, s string) {
// Encode x as an octal ASCII string and write it into b with leading zeros.
func (tw *Writer) octal(b []byte, x int64) {
- s := strconv.Itob64(x, 8)
+ s := strconv.FormatInt(x, 8)
// leading zeros, but leave room for a NUL.
for len(s)+1 < len(b) {
s = "0" + s
@@ -90,7 +90,7 @@ func (tw *Writer) octal(b []byte, x int64) {
// Write x into b, either as octal or as binary (GNUtar/star extension).
func (tw *Writer) numeric(b []byte, x int64) {
// Try octal first.
- s := strconv.Itob64(x, 8)
+ s := strconv.FormatInt(x, 8)
if len(s) < len(b) {
tw.octal(b, x)
return
@@ -107,7 +107,7 @@ func (tw *Writer) numeric(b []byte, x int64) {
// WriteHeader writes hdr and prepares to accept the file's contents.
// WriteHeader calls Flush if it is not the first header.
// Calling after a Close will return ErrWriteAfterClose.
-func (tw *Writer) WriteHeader(hdr *Header) os.Error {
+func (tw *Writer) WriteHeader(hdr *Header) error {
if tw.closed {
return ErrWriteAfterClose
}
@@ -127,19 +127,19 @@ func (tw *Writer) WriteHeader(hdr *Header) os.Error {
// TODO(dsymonds): handle names longer than 100 chars
copy(s.next(100), []byte(hdr.Name))
- tw.octal(s.next(8), hdr.Mode) // 100:108
- tw.numeric(s.next(8), int64(hdr.Uid)) // 108:116
- tw.numeric(s.next(8), int64(hdr.Gid)) // 116:124
- tw.numeric(s.next(12), hdr.Size) // 124:136
- tw.numeric(s.next(12), hdr.Mtime) // 136:148
- s.next(8) // chksum (148:156)
- s.next(1)[0] = hdr.Typeflag // 156:157
- tw.cString(s.next(100), hdr.Linkname) // linkname (157:257)
- copy(s.next(8), []byte("ustar\x0000")) // 257:265
- tw.cString(s.next(32), hdr.Uname) // 265:297
- tw.cString(s.next(32), hdr.Gname) // 297:329
- tw.numeric(s.next(8), hdr.Devmajor) // 329:337
- tw.numeric(s.next(8), hdr.Devminor) // 337:345
+ tw.octal(s.next(8), hdr.Mode) // 100:108
+ tw.numeric(s.next(8), int64(hdr.Uid)) // 108:116
+ tw.numeric(s.next(8), int64(hdr.Gid)) // 116:124
+ tw.numeric(s.next(12), hdr.Size) // 124:136
+ tw.numeric(s.next(12), hdr.ModTime.Unix()) // 136:148
+ s.next(8) // chksum (148:156)
+ s.next(1)[0] = hdr.Typeflag // 156:157
+ tw.cString(s.next(100), hdr.Linkname) // linkname (157:257)
+ copy(s.next(8), []byte("ustar\x0000")) // 257:265
+ tw.cString(s.next(32), hdr.Uname) // 265:297
+ tw.cString(s.next(32), hdr.Gname) // 297:329
+ tw.numeric(s.next(8), hdr.Devmajor) // 329:337
+ tw.numeric(s.next(8), hdr.Devminor) // 337:345
// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
if tw.usedBinary {
@@ -165,7 +165,7 @@ func (tw *Writer) WriteHeader(hdr *Header) os.Error {
// Write writes to the current entry in the tar archive.
// Write returns the error ErrWriteTooLong if more than
// hdr.Size bytes are written after WriteHeader.
-func (tw *Writer) Write(b []byte) (n int, err os.Error) {
+func (tw *Writer) Write(b []byte) (n int, err error) {
if tw.closed {
err = ErrWriteTooLong
return
@@ -187,7 +187,7 @@ func (tw *Writer) Write(b []byte) (n int, err os.Error) {
// Close closes the tar archive, flushing any unwritten
// data to the underlying writer.
-func (tw *Writer) Close() os.Error {
+func (tw *Writer) Close() error {
if tw.err != nil || tw.closed {
return tw.err
}
diff --git a/src/pkg/archive/tar/writer_test.go b/src/pkg/archive/tar/writer_test.go
index 6cc938688..0b413722d 100644
--- a/src/pkg/archive/tar/writer_test.go
+++ b/src/pkg/archive/tar/writer_test.go
@@ -11,6 +11,7 @@ import (
"io/ioutil"
"testing"
"testing/iotest"
+ "time"
)
type writerTestEntry struct {
@@ -28,45 +29,45 @@ var writerTests = []*writerTest{
// tar (GNU tar) 1.26
// ln -s small.txt link.txt
// tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
- &writerTest{
+ {
file: "testdata/writer.tar",
entries: []*writerTestEntry{
- &writerTestEntry{
+ {
header: &Header{
Name: "small.txt",
Mode: 0640,
Uid: 73025,
Gid: 5000,
Size: 5,
- Mtime: 1246508266,
+ ModTime: time.Unix(1246508266, 0),
Typeflag: '0',
Uname: "dsymonds",
Gname: "eng",
},
contents: "Kilts",
},
- &writerTestEntry{
+ {
header: &Header{
Name: "small2.txt",
Mode: 0640,
Uid: 73025,
Gid: 5000,
Size: 11,
- Mtime: 1245217492,
+ ModTime: time.Unix(1245217492, 0),
Typeflag: '0',
Uname: "dsymonds",
Gname: "eng",
},
contents: "Google.com\n",
},
- &writerTestEntry{
+ {
header: &Header{
Name: "link.txt",
Mode: 0777,
Uid: 1000,
Gid: 1000,
Size: 0,
- Mtime: 1314603082,
+ ModTime: time.Unix(1314603082, 0),
Typeflag: '2',
Linkname: "small.txt",
Uname: "strings",
@@ -79,17 +80,17 @@ var writerTests = []*writerTest{
// The truncated test file was produced using these commands:
// dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
// tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
- &writerTest{
+ {
file: "testdata/writer-big.tar",
entries: []*writerTestEntry{
- &writerTestEntry{
+ {
header: &Header{
Name: "tmp/16gig.txt",
Mode: 0640,
Uid: 73025,
Gid: 5000,
Size: 16 << 30,
- Mtime: 1254699560,
+ ModTime: time.Unix(1254699560, 0),
Typeflag: '0',
Uname: "dsymonds",
Gname: "eng",
diff --git a/src/pkg/archive/zip/reader.go b/src/pkg/archive/zip/reader.go
index f92f9297a..4dd0f4f43 100644
--- a/src/pkg/archive/zip/reader.go
+++ b/src/pkg/archive/zip/reader.go
@@ -7,18 +7,19 @@ package zip
import (
"bufio"
"compress/flate"
+ "encoding/binary"
+ "errors"
"hash"
"hash/crc32"
- "encoding/binary"
"io"
"io/ioutil"
"os"
)
var (
- FormatError = os.NewError("zip: not a valid zip file")
- UnsupportedMethod = os.NewError("zip: unsupported compression algorithm")
- ChecksumError = os.NewError("zip: checksum error")
+ ErrFormat = errors.New("zip: not a valid zip file")
+ ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
+ ErrChecksum = errors.New("zip: checksum error")
)
type Reader struct {
@@ -44,7 +45,7 @@ func (f *File) hasDataDescriptor() bool {
}
// OpenReader will open the Zip file specified by name and return a ReadCloser.
-func OpenReader(name string) (*ReadCloser, os.Error) {
+func OpenReader(name string) (*ReadCloser, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
@@ -55,16 +56,17 @@ func OpenReader(name string) (*ReadCloser, os.Error) {
return nil, err
}
r := new(ReadCloser)
- if err := r.init(f, fi.Size); err != nil {
+ if err := r.init(f, fi.Size()); err != nil {
f.Close()
return nil, err
}
+ r.f = f
return r, nil
}
// NewReader returns a new Reader reading from r, which is assumed to
// have the given size in bytes.
-func NewReader(r io.ReaderAt, size int64) (*Reader, os.Error) {
+func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
zr := new(Reader)
if err := zr.init(r, size); err != nil {
return nil, err
@@ -72,7 +74,7 @@ func NewReader(r io.ReaderAt, size int64) (*Reader, os.Error) {
return zr, nil
}
-func (z *Reader) init(r io.ReaderAt, size int64) os.Error {
+func (z *Reader) init(r io.ReaderAt, size int64) error {
end, err := readDirectoryEnd(r, size)
if err != nil {
return err
@@ -88,12 +90,12 @@ func (z *Reader) init(r io.ReaderAt, size int64) os.Error {
// The count of files inside a zip is truncated to fit in a uint16.
// Gloss over this by reading headers until we encounter
- // a bad one, and then only report a FormatError or UnexpectedEOF if
+ // a bad one, and then only report a ErrFormat or UnexpectedEOF if
// the file count modulo 65536 is incorrect.
for {
f := &File{zipr: r, zipsize: size}
err = readDirectoryHeader(f, buf)
- if err == FormatError || err == io.ErrUnexpectedEOF {
+ if err == ErrFormat || err == io.ErrUnexpectedEOF {
break
}
if err != nil {
@@ -110,13 +112,13 @@ func (z *Reader) init(r io.ReaderAt, size int64) os.Error {
}
// Close closes the Zip file, rendering it unusable for I/O.
-func (rc *ReadCloser) Close() os.Error {
+func (rc *ReadCloser) Close() error {
return rc.f.Close()
}
// Open returns a ReadCloser that provides access to the File's contents.
// It is safe to Open and Read from files concurrently.
-func (f *File) Open() (rc io.ReadCloser, err os.Error) {
+func (f *File) Open() (rc io.ReadCloser, err error) {
bodyOffset, err := f.findBodyOffset()
if err != nil {
return
@@ -133,7 +135,7 @@ func (f *File) Open() (rc io.ReadCloser, err os.Error) {
case Deflate:
rc = flate.NewReader(r)
default:
- err = UnsupportedMethod
+ err = ErrAlgorithm
}
if rc != nil {
rc = &checksumReader{rc, crc32.NewIEEE(), f, r}
@@ -148,10 +150,10 @@ type checksumReader struct {
zipr io.Reader // for reading the data descriptor
}
-func (r *checksumReader) Read(b []byte) (n int, err os.Error) {
+func (r *checksumReader) Read(b []byte) (n int, err error) {
n, err = r.rc.Read(b)
r.hash.Write(b[:n])
- if err != os.EOF {
+ if err != io.EOF {
return
}
if r.f.hasDataDescriptor() {
@@ -160,21 +162,21 @@ func (r *checksumReader) Read(b []byte) (n int, err os.Error) {
}
}
if r.hash.Sum32() != r.f.CRC32 {
- err = ChecksumError
+ err = ErrChecksum
}
return
}
-func (r *checksumReader) Close() os.Error { return r.rc.Close() }
+func (r *checksumReader) Close() error { return r.rc.Close() }
-func readFileHeader(f *File, r io.Reader) os.Error {
+func readFileHeader(f *File, r io.Reader) error {
var b [fileHeaderLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
c := binary.LittleEndian
if sig := c.Uint32(b[:4]); sig != fileHeaderSignature {
- return FormatError
+ return ErrFormat
}
f.ReaderVersion = c.Uint16(b[4:6])
f.Flags = c.Uint16(b[6:8])
@@ -197,7 +199,7 @@ func readFileHeader(f *File, r io.Reader) os.Error {
// findBodyOffset does the minimum work to verify the file has a header
// and returns the file body offset.
-func (f *File) findBodyOffset() (int64, os.Error) {
+func (f *File) findBodyOffset() (int64, error) {
r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset)
var b [fileHeaderLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
@@ -205,7 +207,7 @@ func (f *File) findBodyOffset() (int64, os.Error) {
}
c := binary.LittleEndian
if sig := c.Uint32(b[:4]); sig != fileHeaderSignature {
- return 0, FormatError
+ return 0, ErrFormat
}
filenameLen := int(c.Uint16(b[26:28]))
extraLen := int(c.Uint16(b[28:30]))
@@ -214,15 +216,15 @@ func (f *File) findBodyOffset() (int64, os.Error) {
// readDirectoryHeader attempts to read a directory header from r.
// It returns io.ErrUnexpectedEOF if it cannot read a complete header,
-// and FormatError if it doesn't find a valid header signature.
-func readDirectoryHeader(f *File, r io.Reader) os.Error {
+// and ErrFormat if it doesn't find a valid header signature.
+func readDirectoryHeader(f *File, r io.Reader) error {
var b [directoryHeaderLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
c := binary.LittleEndian
if sig := c.Uint32(b[:4]); sig != directoryHeaderSignature {
- return FormatError
+ return ErrFormat
}
f.CreatorVersion = c.Uint16(b[4:6])
f.ReaderVersion = c.Uint16(b[6:8])
@@ -238,7 +240,7 @@ func readDirectoryHeader(f *File, r io.Reader) os.Error {
commentLen := int(c.Uint16(b[32:34]))
// startDiskNumber := c.Uint16(b[34:36]) // 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]))
d := make([]byte, filenameLen+extraLen+commentLen)
if _, err := io.ReadFull(r, d); err != nil {
@@ -250,7 +252,7 @@ func readDirectoryHeader(f *File, r io.Reader) os.Error {
return nil
}
-func readDataDescriptor(r io.Reader, f *File) os.Error {
+func readDataDescriptor(r io.Reader, f *File) error {
var b [dataDescriptorLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
@@ -262,7 +264,7 @@ func readDataDescriptor(r io.Reader, f *File) os.Error {
return nil
}
-func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err os.Error) {
+func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) {
// look for directoryEndSignature in the last 1k, then in the last 65k
var b []byte
for i, bLen := range []int64{1024, 65 * 1024} {
@@ -270,7 +272,7 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err os.Erro
bLen = size
}
b = make([]byte, int(bLen))
- if _, err := r.ReadAt(b, size-bLen); err != nil && err != os.EOF {
+ if _, err := r.ReadAt(b, size-bLen); err != nil && err != io.EOF {
return nil, err
}
if p := findSignatureInBlock(b); p >= 0 {
@@ -278,7 +280,7 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err os.Erro
break
}
if i == 1 || bLen == size {
- return nil, FormatError
+ return nil, ErrFormat
}
}
diff --git a/src/pkg/archive/zip/reader_test.go b/src/pkg/archive/zip/reader_test.go
index fd5fed2af..9407e35d5 100644
--- a/src/pkg/archive/zip/reader_test.go
+++ b/src/pkg/archive/zip/reader_test.go
@@ -18,7 +18,7 @@ type ZipTest struct {
Name string
Comment string
File []ZipTestFile
- Error os.Error // the error that Opening this file should return
+ Error error // the error that Opening this file should return
}
type ZipTestFile struct {
@@ -26,6 +26,7 @@ type ZipTestFile struct {
Content []byte // if blank, will attempt to compare against File
File string // name of file to compare to (relative to testdata/)
Mtime string // modified time in format "mm-dd-yy hh:mm:ss"
+ Mode os.FileMode
}
// Caution: The Mtime values found for the test files should correspond to
@@ -47,11 +48,13 @@ var tests = []ZipTest{
Name: "test.txt",
Content: []byte("This is a test text file.\n"),
Mtime: "09-05-10 12:12:02",
+ Mode: 0644,
},
{
Name: "gophercolor16x16.png",
File: "gophercolor16x16.png",
Mtime: "09-05-10 15:52:58",
+ Mode: 0644,
},
},
},
@@ -62,11 +65,12 @@ var tests = []ZipTest{
Name: "r/r.zip",
File: "r.zip",
Mtime: "03-04-10 00:24:16",
+ Mode: 0666,
},
},
},
{Name: "readme.zip"},
- {Name: "readme.notzip", Error: FormatError},
+ {Name: "readme.notzip", Error: ErrFormat},
{
Name: "dd.zip",
File: []ZipTestFile{
@@ -74,9 +78,43 @@ var tests = []ZipTest{
Name: "filename",
Content: []byte("This is a test textfile.\n"),
Mtime: "02-02-11 13:06:20",
+ Mode: 0666,
},
},
},
+ {
+ // created in windows XP file manager.
+ Name: "winxp.zip",
+ File: crossPlatform,
+ },
+ {
+ // created by Zip 3.0 under Linux
+ Name: "unix.zip",
+ File: crossPlatform,
+ },
+}
+
+var crossPlatform = []ZipTestFile{
+ {
+ Name: "hello",
+ Content: []byte("world \r\n"),
+ Mode: 0666,
+ },
+ {
+ Name: "dir/bar",
+ Content: []byte("foo \r\n"),
+ Mode: 0666,
+ },
+ {
+ Name: "dir/empty/",
+ Content: []byte{},
+ Mode: os.ModeDir | 0777,
+ },
+ {
+ Name: "readonly",
+ Content: []byte("important \r\n"),
+ Mode: 0444,
+ },
}
func TestReader(t *testing.T) {
@@ -93,10 +131,14 @@ func readTestZip(t *testing.T, zt ZipTest) {
}
// bail if file is not zip
- if err == FormatError {
+ if err == ErrFormat {
return
}
- defer z.Close()
+ defer func() {
+ if err := z.Close(); err != nil {
+ t.Errorf("error %q when closing zip file", err)
+ }
+ }()
// bail here if no Files expected to be tested
// (there may actually be files in the zip, but we don't care)
@@ -121,10 +163,10 @@ func readTestZip(t *testing.T, zt ZipTest) {
done := make(chan bool)
for i := 0; i < 5; i++ {
for j, ft := range zt.File {
- go func() {
+ go func(j int, ft ZipTestFile) {
readTestFile(t, ft, z.File[j])
done <- true
- }()
+ }(j, ft)
n++
}
}
@@ -142,8 +184,8 @@ func readTestZip(t *testing.T, zt ZipTest) {
}
var b bytes.Buffer
_, err = io.Copy(&b, r)
- if err != ChecksumError {
- t.Errorf("%s: copy error=%v, want %v", z.File[0].Name, err, ChecksumError)
+ if err != ErrChecksum {
+ t.Errorf("%s: copy error=%v, want %v", z.File[0].Name, err, ErrChecksum)
}
}
}
@@ -153,15 +195,19 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
t.Errorf("name=%q, want %q", f.Name, ft.Name)
}
- mtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime)
- if err != nil {
- t.Error(err)
- return
- }
- if got, want := f.Mtime_ns()/1e9, mtime.Seconds(); got != want {
- t.Errorf("%s: mtime=%s (%d); want %s (%d)", f.Name, time.SecondsToUTC(got), got, mtime, want)
+ if ft.Mtime != "" {
+ mtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if ft := f.ModTime(); !ft.Equal(mtime) {
+ t.Errorf("%s: mtime=%s, want %s", f.Name, ft, mtime)
+ }
}
+ testFileMode(t, f, ft.Mode)
+
size0 := f.UncompressedSize
var b bytes.Buffer
@@ -183,7 +229,7 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
r.Close()
var c []byte
- if len(ft.Content) != 0 {
+ if ft.Content != nil {
c = ft.Content
} else if c, err = ioutil.ReadFile("testdata/" + ft.File); err != nil {
t.Error(err)
@@ -203,14 +249,23 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
}
}
+func testFileMode(t *testing.T, f *File, want os.FileMode) {
+ mode := f.Mode()
+ if want == 0 {
+ t.Errorf("%s mode: got %v, want none", f.Name, mode)
+ } else if mode != want {
+ t.Errorf("%s mode: want %v, got %v", f.Name, want, mode)
+ }
+}
+
func TestInvalidFiles(t *testing.T) {
const size = 1024 * 70 // 70kb
b := make([]byte, size)
// zeroes
_, err := NewReader(sliceReaderAt(b), size)
- if err != FormatError {
- t.Errorf("zeroes: error=%v, want %v", err, FormatError)
+ if err != ErrFormat {
+ t.Errorf("zeroes: error=%v, want %v", err, ErrFormat)
}
// repeated directoryEndSignatures
@@ -220,14 +275,14 @@ func TestInvalidFiles(t *testing.T) {
copy(b[i:i+4], sig)
}
_, err = NewReader(sliceReaderAt(b), size)
- if err != FormatError {
- t.Errorf("sigs: error=%v, want %v", err, FormatError)
+ if err != ErrFormat {
+ t.Errorf("sigs: error=%v, want %v", err, ErrFormat)
}
}
type sliceReaderAt []byte
-func (r sliceReaderAt) ReadAt(b []byte, off int64) (int, os.Error) {
+func (r sliceReaderAt) ReadAt(b []byte, off int64) (int, error) {
copy(b, r[int(off):int(off)+len(b)])
return len(b), nil
}
diff --git a/src/pkg/archive/zip/struct.go b/src/pkg/archive/zip/struct.go
index 1d6e70f10..67e965862 100644
--- a/src/pkg/archive/zip/struct.go
+++ b/src/pkg/archive/zip/struct.go
@@ -11,8 +11,11 @@ This package does not support ZIP64 or disk spanning.
*/
package zip
-import "os"
-import "time"
+import (
+ "errors"
+ "os"
+ "time"
+)
// Compression methods.
const (
@@ -28,6 +31,13 @@ const (
directoryHeaderLen = 46 // + filename + extra + comment
directoryEndLen = 22 // + comment
dataDescriptorLen = 12
+
+ // Constants for the first byte in CreatorVersion
+ creatorFAT = 0
+ creatorUnix = 3
+ creatorNTFS = 11
+ creatorVFAT = 14
+ creatorMacOSX = 19
)
type FileHeader struct {
@@ -42,9 +52,42 @@ type FileHeader struct {
CompressedSize uint32
UncompressedSize uint32
Extra []byte
+ ExternalAttrs uint32 // Meaning depends on CreatorVersion
Comment string
}
+// FileInfo returns an os.FileInfo for the FileHeader.
+func (fh *FileHeader) FileInfo() os.FileInfo {
+ return headerFileInfo{fh}
+}
+
+// headerFileInfo implements os.FileInfo.
+type headerFileInfo struct {
+ fh *FileHeader
+}
+
+func (fi headerFileInfo) Name() string { return fi.fh.Name }
+func (fi headerFileInfo) Size() int64 { return int64(fi.fh.UncompressedSize) }
+func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
+func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
+func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() }
+
+// FileInfoHeader creates a partially-populated FileHeader from an
+// os.FileInfo.
+func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
+ size := fi.Size()
+ if size > (1<<32 - 1) {
+ return nil, errors.New("zip: file over 4GB")
+ }
+ fh := &FileHeader{
+ Name: fi.Name(),
+ UncompressedSize: uint32(size),
+ }
+ fh.SetModTime(fi.ModTime())
+ fh.SetMode(fi.Mode())
+ return fh, nil
+}
+
type directoryEnd struct {
diskNbr uint16 // unused
dirDiskNbr uint16 // unused
@@ -56,10 +99,10 @@ type directoryEnd struct {
comment string
}
-func recoverError(err *os.Error) {
+func recoverError(errp *error) {
if e := recover(); e != nil {
- if osErr, ok := e.(os.Error); ok {
- *err = osErr
+ if err, ok := e.(error); ok {
+ *errp = err
return
}
panic(e)
@@ -70,22 +113,122 @@ func recoverError(err *os.Error) {
// The resolution is 2s.
// See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx
func msDosTimeToTime(dosDate, dosTime uint16) time.Time {
- return time.Time{
+ return time.Date(
// date bits 0-4: day of month; 5-8: month; 9-15: years since 1980
- Year: int64(dosDate>>9 + 1980),
- Month: int(dosDate >> 5 & 0xf),
- Day: int(dosDate & 0x1f),
+ int(dosDate>>9+1980),
+ time.Month(dosDate>>5&0xf),
+ int(dosDate&0x1f),
// time bits 0-4: second/2; 5-10: minute; 11-15: hour
- Hour: int(dosTime >> 11),
- Minute: int(dosTime >> 5 & 0x3f),
- Second: int(dosTime & 0x1f * 2),
- }
+ int(dosTime>>11),
+ int(dosTime>>5&0x3f),
+ int(dosTime&0x1f*2),
+ 0, // nanoseconds
+
+ time.UTC,
+ )
+}
+
+// timeToMsDosTime converts a time.Time to an MS-DOS date and time.
+// The resolution is 2s.
+// See: http://msdn.microsoft.com/en-us/library/ms724274(v=VS.85).aspx
+func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) {
+ t = t.In(time.UTC)
+ fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9)
+ fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11)
+ return
}
-// Mtime_ns returns the modified time in ns since epoch.
+// ModTime returns the modification time.
// The resolution is 2s.
-func (h *FileHeader) Mtime_ns() int64 {
- t := msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
- return t.Seconds() * 1e9
+func (h *FileHeader) ModTime() time.Time {
+ return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
+}
+
+// SetModTime sets the ModifiedTime and ModifiedDate fields to the given time.
+// The resolution is 2s.
+func (h *FileHeader) SetModTime(t time.Time) {
+ h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t)
+}
+
+// traditional names for Unix constants
+const (
+ s_IFMT = 0xf000
+ s_IFDIR = 0x4000
+ s_IFREG = 0x8000
+ s_ISUID = 0x800
+ s_ISGID = 0x400
+
+ msdosDir = 0x10
+ msdosReadOnly = 0x01
+)
+
+// Mode returns the permission and mode bits for the FileHeader.
+func (h *FileHeader) Mode() (mode os.FileMode) {
+ switch h.CreatorVersion >> 8 {
+ case creatorUnix, creatorMacOSX:
+ mode = unixModeToFileMode(h.ExternalAttrs >> 16)
+ case creatorNTFS, creatorVFAT, creatorFAT:
+ mode = msdosModeToFileMode(h.ExternalAttrs)
+ }
+ if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
+ mode |= os.ModeDir
+ }
+ return mode
+}
+
+// SetMode changes the permission and mode bits for the FileHeader.
+func (h *FileHeader) SetMode(mode os.FileMode) {
+ h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
+ h.ExternalAttrs = fileModeToUnixMode(mode) << 16
+
+ // set MSDOS attributes too, as the original zip does.
+ if mode&os.ModeDir != 0 {
+ h.ExternalAttrs |= msdosDir
+ }
+ if mode&0200 == 0 {
+ h.ExternalAttrs |= msdosReadOnly
+ }
+}
+
+func msdosModeToFileMode(m uint32) (mode os.FileMode) {
+ if m&msdosDir != 0 {
+ mode = os.ModeDir | 0777
+ } else {
+ mode = 0666
+ }
+ if m&msdosReadOnly != 0 {
+ mode &^= 0222
+ }
+ return mode
+}
+
+func fileModeToUnixMode(mode os.FileMode) uint32 {
+ var m uint32
+ if mode&os.ModeDir != 0 {
+ m = s_IFDIR
+ } else {
+ m = s_IFREG
+ }
+ if mode&os.ModeSetuid != 0 {
+ m |= s_ISUID
+ }
+ if mode&os.ModeSetgid != 0 {
+ m |= s_ISGID
+ }
+ return m | uint32(mode&0777)
+}
+
+func unixModeToFileMode(m uint32) os.FileMode {
+ var mode os.FileMode
+ if m&s_IFMT == s_IFDIR {
+ mode |= os.ModeDir
+ }
+ if m&s_ISGID != 0 {
+ mode |= os.ModeSetgid
+ }
+ if m&s_ISUID != 0 {
+ mode |= os.ModeSetuid
+ }
+ return mode | os.FileMode(m&0777)
}
diff --git a/src/pkg/archive/zip/testdata/unix.zip b/src/pkg/archive/zip/testdata/unix.zip
new file mode 100644
index 000000000..ce1a981b2
--- /dev/null
+++ b/src/pkg/archive/zip/testdata/unix.zip
Binary files differ
diff --git a/src/pkg/archive/zip/testdata/winxp.zip b/src/pkg/archive/zip/testdata/winxp.zip
new file mode 100644
index 000000000..3919322f0
--- /dev/null
+++ b/src/pkg/archive/zip/testdata/winxp.zip
Binary files differ
diff --git a/src/pkg/archive/zip/writer.go b/src/pkg/archive/zip/writer.go
index 2065b06da..b1b128e2a 100644
--- a/src/pkg/archive/zip/writer.go
+++ b/src/pkg/archive/zip/writer.go
@@ -8,10 +8,10 @@ import (
"bufio"
"compress/flate"
"encoding/binary"
+ "errors"
"hash"
"hash/crc32"
"io"
- "os"
)
// TODO(adg): support zip file comments
@@ -37,7 +37,7 @@ func NewWriter(w io.Writer) *Writer {
// Close finishes writing the zip file by writing the central directory.
// It does not (and can not) close the underlying writer.
-func (w *Writer) Close() (err os.Error) {
+func (w *Writer) Close() (err error) {
if w.last != nil && !w.last.closed {
if err = w.last.close(); err != nil {
return
@@ -45,7 +45,7 @@ func (w *Writer) Close() (err os.Error) {
w.last = nil
}
if w.closed {
- return os.NewError("zip: writer closed twice")
+ return errors.New("zip: writer closed twice")
}
w.closed = true
@@ -69,7 +69,7 @@ func (w *Writer) Close() (err os.Error) {
write(w, uint16(len(h.Comment)))
write(w, uint16(0)) // disk number start
write(w, uint16(0)) // internal file attributes
- write(w, uint32(0)) // external file attributes
+ write(w, h.ExternalAttrs)
write(w, h.offset)
writeBytes(w, []byte(h.Name))
writeBytes(w, h.Extra)
@@ -94,7 +94,7 @@ func (w *Writer) Close() (err os.Error) {
// It returns a Writer to which the file contents should be written.
// The file's contents must be written to the io.Writer before the next
// call to Create, CreateHeader, or Close.
-func (w *Writer) Create(name string) (io.Writer, os.Error) {
+func (w *Writer) Create(name string) (io.Writer, error) {
header := &FileHeader{
Name: name,
Method: Deflate,
@@ -107,7 +107,7 @@ func (w *Writer) Create(name string) (io.Writer, os.Error) {
// It returns a Writer to which the file contents should be written.
// The file's contents must be written to the io.Writer before the next
// call to Create, CreateHeader, or Close.
-func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, os.Error) {
+func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
if w.last != nil && !w.last.closed {
if err := w.last.close(); err != nil {
return nil, err
@@ -115,7 +115,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, os.Error) {
}
fh.Flags |= 0x8 // we will write a data descriptor
- fh.CreatorVersion = 0x14
+ fh.CreatorVersion = fh.CreatorVersion&0xff00 | 0x14
fh.ReaderVersion = 0x14
fw := &fileWriter{
@@ -129,7 +129,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, os.Error) {
case Deflate:
fw.comp = flate.NewWriter(fw.compCount, 5)
default:
- return nil, UnsupportedMethod
+ return nil, ErrAlgorithm
}
fw.rawCount = &countWriter{w: fw.comp}
@@ -148,7 +148,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, os.Error) {
return fw, nil
}
-func writeHeader(w io.Writer, h *FileHeader) (err os.Error) {
+func writeHeader(w io.Writer, h *FileHeader) (err error) {
defer recoverError(&err)
write(w, uint32(fileHeaderSignature))
write(w, h.ReaderVersion)
@@ -176,17 +176,17 @@ type fileWriter struct {
closed bool
}
-func (w *fileWriter) Write(p []byte) (int, os.Error) {
+func (w *fileWriter) Write(p []byte) (int, error) {
if w.closed {
- return 0, os.NewError("zip: write to closed file")
+ return 0, errors.New("zip: write to closed file")
}
w.crc32.Write(p)
return w.rawCount.Write(p)
}
-func (w *fileWriter) close() (err os.Error) {
+func (w *fileWriter) close() (err error) {
if w.closed {
- return os.NewError("zip: file closed twice")
+ return errors.New("zip: file closed twice")
}
w.closed = true
if err = w.comp.Close(); err != nil {
@@ -213,7 +213,7 @@ type countWriter struct {
count int64
}
-func (w *countWriter) Write(p []byte) (int, os.Error) {
+func (w *countWriter) Write(p []byte) (int, error) {
n, err := w.w.Write(p)
w.count += int64(n)
return n, err
@@ -223,7 +223,7 @@ type nopCloser struct {
io.Writer
}
-func (w nopCloser) Close() os.Error {
+func (w nopCloser) Close() error {
return nil
}
diff --git a/src/pkg/archive/zip/writer_test.go b/src/pkg/archive/zip/writer_test.go
index eb2a80c3f..5a576b1c3 100644
--- a/src/pkg/archive/zip/writer_test.go
+++ b/src/pkg/archive/zip/writer_test.go
@@ -7,25 +7,71 @@ package zip
import (
"bytes"
"io/ioutil"
- "rand"
+ "math/rand"
+ "os"
"testing"
)
// 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 os.FileMode
+}
+
+var writeTests = []WriteTest{
+ {
+ Name: "foo",
+ Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),
+ Method: Store,
+ Mode: 0666,
+ },
+ {
+ Name: "bar",
+ Data: nil, // large data set in the test
+ Method: Deflate,
+ Mode: 0644,
+ },
+ {
+ Name: "setuid",
+ Data: []byte("setuid file"),
+ Method: Deflate,
+ Mode: 0755 | os.ModeSetuid,
+ },
+ {
+ Name: "setgid",
+ Data: []byte("setgid file"),
+ Method: Deflate,
+ Mode: 0755 | os.ModeSetgid,
+ },
+ {
+ Name: "setgid",
+ Data: []byte("setgid file"),
+ Method: Deflate,
+ Mode: 0755 | os.ModeSetgid,
+ },
+}
func TestWriter(t *testing.T) {
largeData := make([]byte, 1<<17)
for i := range largeData {
largeData[i] = byte(rand.Int())
}
+ writeTests[1].Data = largeData
+ defer func() {
+ writeTests[1].Data = nil
+ }()
// write a zip file
buf := new(bytes.Buffer)
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 {
t.Fatal(err)
}
@@ -35,26 +81,34 @@ func TestWriter(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- testReadFile(t, r.File[0], []byte(testString))
- testReadFile(t, r.File[1], largeData)
+ for i, wt := range writeTests {
+ 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{
- Name: name,
- Method: method,
+ Name: wt.Name,
+ Method: wt.Method,
+ }
+ if wt.Mode != 0 {
+ header.SetMode(wt.Mode)
}
f, err := w.CreateHeader(header)
if err != nil {
t.Fatal(err)
}
- _, err = f.Write(data)
+ _, err = f.Write(wt.Data)
if err != nil {
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.Fatalf("File name: got %q, want %q", f.Name, wt.Name)
+ }
+ testFileMode(t, f, wt.Mode)
rc, err := f.Open()
if err != nil {
t.Fatal("opening:", err)
@@ -67,7 +121,7 @@ func testReadFile(t *testing.T, f *File, data []byte) {
if err != nil {
t.Fatal("closing:", err)
}
- if !bytes.Equal(b, data) {
- t.Errorf("File contents %q, want %q", b, data)
+ if !bytes.Equal(b, wt.Data) {
+ t.Errorf("File contents %q, want %q", b, wt.Data)
}
}
diff --git a/src/pkg/archive/zip/zip_test.go b/src/pkg/archive/zip/zip_test.go
index 0f71fdfac..acd3d9382 100644
--- a/src/pkg/archive/zip/zip_test.go
+++ b/src/pkg/archive/zip/zip_test.go
@@ -9,15 +9,17 @@ package zip
import (
"bytes"
"fmt"
- "os"
+ "io"
+ "reflect"
"testing"
+ "time"
)
type stringReaderAt string
-func (s stringReaderAt) ReadAt(p []byte, off int64) (n int, err os.Error) {
+func (s stringReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
if off >= int64(len(s)) {
- return 0, os.EOF
+ return 0, io.EOF
}
n = copy(p, s[off:])
return
@@ -55,3 +57,32 @@ func TestOver65kFiles(t *testing.T) {
}
}
}
+
+func TestModTime(t *testing.T) {
+ var testTime = time.Date(2009, time.November, 10, 23, 45, 58, 0, time.UTC)
+ fh := new(FileHeader)
+ fh.SetModTime(testTime)
+ outTime := fh.ModTime()
+ if !outTime.Equal(testTime) {
+ t.Errorf("times don't match: got %s, want %s", outTime, testTime)
+ }
+}
+
+func TestFileHeaderRoundTrip(t *testing.T) {
+ fh := &FileHeader{
+ Name: "foo.txt",
+ UncompressedSize: 987654321,
+ ModifiedTime: 1234,
+ ModifiedDate: 5678,
+ }
+ fi := fh.FileInfo()
+ fh2, err := FileInfoHeader(fi)
+
+ // Ignore these fields:
+ fh2.CreatorVersion = 0
+ fh2.ExternalAttrs = 0
+
+ if !reflect.DeepEqual(fh, fh2) {
+ t.Errorf("mismatch\n input=%#v\noutput=%#v\nerr=%v", fh, fh2, err)
+ }
+}