diff options
Diffstat (limited to 'libgo/go/archive')
-rw-r--r-- | libgo/go/archive/tar/reader.go | 2 | ||||
-rw-r--r-- | libgo/go/archive/tar/reader_test.go | 1 | ||||
-rw-r--r-- | libgo/go/archive/zip/reader.go | 281 | ||||
-rw-r--r-- | libgo/go/archive/zip/reader_test.go | 44 | ||||
-rw-r--r-- | libgo/go/archive/zip/struct.go | 61 | ||||
-rw-r--r-- | libgo/go/archive/zip/writer.go | 244 | ||||
-rw-r--r-- | libgo/go/archive/zip/writer_test.go | 73 | ||||
-rw-r--r-- | libgo/go/archive/zip/zip_test.go | 57 |
8 files changed, 607 insertions, 156 deletions
diff --git a/libgo/go/archive/tar/reader.go b/libgo/go/archive/tar/reader.go index ad06b6dac54..45d95c3df29 100644 --- a/libgo/go/archive/tar/reader.go +++ b/libgo/go/archive/tar/reader.go @@ -16,7 +16,7 @@ import ( ) var ( - HeaderError os.Error = os.ErrorString("invalid tar header") + HeaderError = os.NewError("invalid tar header") ) // A Reader provides sequential access to the contents of a tar archive. diff --git a/libgo/go/archive/tar/reader_test.go b/libgo/go/archive/tar/reader_test.go index 32fc8f9151a..f473c900f26 100644 --- a/libgo/go/archive/tar/reader_test.go +++ b/libgo/go/archive/tar/reader_test.go @@ -178,7 +178,6 @@ func TestPartialRead(t *testing.T) { } } - func TestIncrementalRead(t *testing.T) { test := gnuTarTest f, err := os.Open(test.file) diff --git a/libgo/go/archive/zip/reader.go b/libgo/go/archive/zip/reader.go index 17464c5d8e4..f92f9297ada 100644 --- a/libgo/go/archive/zip/reader.go +++ b/libgo/go/archive/zip/reader.go @@ -2,18 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -/* -Package zip provides support for reading ZIP archives. - -See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT - -This package does not support ZIP64 or disk spanning. -*/ package zip import ( "bufio" - "bytes" "compress/flate" "hash" "hash/crc32" @@ -24,9 +16,9 @@ import ( ) var ( - FormatError = os.NewError("not a valid zip file") - UnsupportedMethod = os.NewError("unsupported compression algorithm") - ChecksumError = os.NewError("checksum error") + FormatError = os.NewError("zip: not a valid zip file") + UnsupportedMethod = os.NewError("zip: unsupported compression algorithm") + ChecksumError = os.NewError("zip: checksum error") ) type Reader struct { @@ -44,15 +36,14 @@ type File struct { FileHeader zipr io.ReaderAt zipsize int64 - headerOffset uint32 - bodyOffset int64 + headerOffset int64 } func (f *File) hasDataDescriptor() bool { return f.Flags&0x8 != 0 } -// OpenReader will open the Zip file specified by name and return a ReaderCloser. +// OpenReader will open the Zip file specified by name and return a ReadCloser. func OpenReader(name string) (*ReadCloser, os.Error) { f, err := os.Open(name) if err != nil { @@ -87,18 +78,33 @@ func (z *Reader) init(r io.ReaderAt, size int64) os.Error { return err } z.r = r - z.File = make([]*File, end.directoryRecords) + z.File = make([]*File, 0, end.directoryRecords) z.Comment = end.comment rs := io.NewSectionReader(r, 0, size) if _, err = rs.Seek(int64(end.directoryOffset), os.SEEK_SET); err != nil { return err } buf := bufio.NewReader(rs) - for i := range z.File { - z.File[i] = &File{zipr: r, zipsize: size} - if err := readDirectoryHeader(z.File[i], buf); err != nil { + + // 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 + // the file count modulo 65536 is incorrect. + for { + f := &File{zipr: r, zipsize: size} + err = readDirectoryHeader(f, buf) + if err == FormatError || err == io.ErrUnexpectedEOF { + break + } + if err != nil { return err } + z.File = append(z.File, f) + } + if uint16(len(z.File)) != end.directoryRecords { + // Return the readDirectoryHeader error if we read + // the wrong number of directory entries. + return err } return nil } @@ -109,31 +115,22 @@ func (rc *ReadCloser) Close() os.Error { } // 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) { - off := int64(f.headerOffset) - if f.bodyOffset == 0 { - r := io.NewSectionReader(f.zipr, off, f.zipsize-off) - if err = readFileHeader(f, r); err != nil { - return - } - if f.bodyOffset, err = r.Seek(0, os.SEEK_CUR); err != nil { - return - } + bodyOffset, err := f.findBodyOffset() + if err != nil { + return } size := int64(f.CompressedSize) - if f.hasDataDescriptor() { - if size == 0 { - // permit SectionReader to see the rest of the file - size = f.zipsize - (off + f.bodyOffset) - } else { - size += dataDescriptorLen - } + if size == 0 && f.hasDataDescriptor() { + // permit SectionReader to see the rest of the file + size = f.zipsize - (f.headerOffset + bodyOffset) } - r := io.NewSectionReader(f.zipr, off+f.bodyOffset, size) + r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size) switch f.Method { - case 0: // store (no compression) + case Store: // (no compression) rc = ioutil.NopCloser(r) - case 8: // DEFLATE + case Deflate: rc = flate.NewReader(r) default: err = UnsupportedMethod @@ -170,90 +167,102 @@ func (r *checksumReader) Read(b []byte) (n int, err os.Error) { func (r *checksumReader) Close() os.Error { return r.rc.Close() } -func readFileHeader(f *File, r io.Reader) (err os.Error) { - defer func() { - if rerr, ok := recover().(os.Error); ok { - err = rerr - } - }() - var ( - signature uint32 - filenameLength uint16 - extraLength uint16 - ) - read(r, &signature) - if signature != fileHeaderSignature { +func readFileHeader(f *File, r io.Reader) os.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 } - read(r, &f.ReaderVersion) - read(r, &f.Flags) - read(r, &f.Method) - read(r, &f.ModifiedTime) - read(r, &f.ModifiedDate) - read(r, &f.CRC32) - read(r, &f.CompressedSize) - read(r, &f.UncompressedSize) - read(r, &filenameLength) - read(r, &extraLength) - f.Name = string(readByteSlice(r, filenameLength)) - f.Extra = readByteSlice(r, extraLength) - return + f.ReaderVersion = c.Uint16(b[4:6]) + f.Flags = c.Uint16(b[6:8]) + f.Method = c.Uint16(b[8:10]) + f.ModifiedTime = c.Uint16(b[10:12]) + f.ModifiedDate = c.Uint16(b[12:14]) + f.CRC32 = c.Uint32(b[14:18]) + f.CompressedSize = c.Uint32(b[18:22]) + f.UncompressedSize = c.Uint32(b[22:26]) + filenameLen := int(c.Uint16(b[26:28])) + extraLen := int(c.Uint16(b[28:30])) + d := make([]byte, filenameLen+extraLen) + if _, err := io.ReadFull(r, d); err != nil { + return err + } + f.Name = string(d[:filenameLen]) + f.Extra = d[filenameLen:] + return nil } -func readDirectoryHeader(f *File, r io.Reader) (err os.Error) { - defer func() { - if rerr, ok := recover().(os.Error); ok { - err = rerr - } - }() - var ( - signature uint32 - filenameLength uint16 - extraLength uint16 - commentLength uint16 - startDiskNumber uint16 // unused - internalAttributes uint16 // unused - externalAttributes uint32 // unused - ) - read(r, &signature) - if signature != directoryHeaderSignature { +// 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) { + r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset) + var b [fileHeaderLen]byte + if _, err := io.ReadFull(r, b[:]); err != nil { + return 0, err + } + c := binary.LittleEndian + if sig := c.Uint32(b[:4]); sig != fileHeaderSignature { + return 0, FormatError + } + filenameLen := int(c.Uint16(b[26:28])) + extraLen := int(c.Uint16(b[28:30])) + return int64(fileHeaderLen + filenameLen + extraLen), nil +} + +// 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 { + 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 } - read(r, &f.CreatorVersion) - read(r, &f.ReaderVersion) - read(r, &f.Flags) - read(r, &f.Method) - read(r, &f.ModifiedTime) - read(r, &f.ModifiedDate) - read(r, &f.CRC32) - read(r, &f.CompressedSize) - read(r, &f.UncompressedSize) - read(r, &filenameLength) - read(r, &extraLength) - read(r, &commentLength) - read(r, &startDiskNumber) - read(r, &internalAttributes) - read(r, &externalAttributes) - read(r, &f.headerOffset) - f.Name = string(readByteSlice(r, filenameLength)) - f.Extra = readByteSlice(r, extraLength) - f.Comment = string(readByteSlice(r, commentLength)) - return + f.CreatorVersion = c.Uint16(b[4:6]) + f.ReaderVersion = c.Uint16(b[6:8]) + f.Flags = c.Uint16(b[8:10]) + f.Method = c.Uint16(b[10:12]) + f.ModifiedTime = c.Uint16(b[12:14]) + f.ModifiedDate = c.Uint16(b[14:16]) + f.CRC32 = c.Uint32(b[16:20]) + f.CompressedSize = c.Uint32(b[20:24]) + f.UncompressedSize = c.Uint32(b[24:28]) + filenameLen := int(c.Uint16(b[28:30])) + extraLen := int(c.Uint16(b[30:32])) + 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.headerOffset = int64(c.Uint32(b[42:46])) + d := make([]byte, filenameLen+extraLen+commentLen) + if _, err := io.ReadFull(r, d); err != nil { + return err + } + f.Name = string(d[:filenameLen]) + f.Extra = d[filenameLen : filenameLen+extraLen] + f.Comment = string(d[filenameLen+extraLen:]) + return nil } -func readDataDescriptor(r io.Reader, f *File) (err os.Error) { - defer func() { - if rerr, ok := recover().(os.Error); ok { - err = rerr - } - }() - read(r, &f.CRC32) - read(r, &f.CompressedSize) - read(r, &f.UncompressedSize) - return +func readDataDescriptor(r io.Reader, f *File) os.Error { + var b [dataDescriptorLen]byte + if _, err := io.ReadFull(r, b[:]); err != nil { + return err + } + c := binary.LittleEndian + f.CRC32 = c.Uint32(b[:4]) + f.CompressedSize = c.Uint32(b[4:8]) + f.UncompressedSize = c.Uint32(b[8:12]) + return nil } -func readDirectoryEnd(r io.ReaderAt, size int64) (d *directoryEnd, err os.Error) { +func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err os.Error) { // look for directoryEndSignature in the last 1k, then in the last 65k var b []byte for i, bLen := range []int64{1024, 65 * 1024} { @@ -274,53 +283,29 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (d *directoryEnd, err os.Error) } // read header into struct - defer func() { - if rerr, ok := recover().(os.Error); ok { - err = rerr - d = nil - } - }() - br := bytes.NewBuffer(b[4:]) // skip over signature - d = new(directoryEnd) - read(br, &d.diskNbr) - read(br, &d.dirDiskNbr) - read(br, &d.dirRecordsThisDisk) - read(br, &d.directoryRecords) - read(br, &d.directorySize) - read(br, &d.directoryOffset) - read(br, &d.commentLen) - d.comment = string(readByteSlice(br, d.commentLen)) + c := binary.LittleEndian + d := new(directoryEnd) + d.diskNbr = c.Uint16(b[4:6]) + d.dirDiskNbr = c.Uint16(b[6:8]) + d.dirRecordsThisDisk = c.Uint16(b[8:10]) + d.directoryRecords = c.Uint16(b[10:12]) + d.directorySize = c.Uint32(b[12:16]) + d.directoryOffset = c.Uint32(b[16:20]) + d.commentLen = c.Uint16(b[20:22]) + d.comment = string(b[22 : 22+int(d.commentLen)]) return d, nil } func findSignatureInBlock(b []byte) int { - const minSize = 4 + 2 + 2 + 2 + 2 + 4 + 4 + 2 // fixed part of header - for i := len(b) - minSize; i >= 0; i-- { + for i := len(b) - directoryEndLen; i >= 0; i-- { // defined from directoryEndSignature in struct.go if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 { // n is length of comment - n := int(b[i+minSize-2]) | int(b[i+minSize-1])<<8 - if n+minSize+i == len(b) { + n := int(b[i+directoryEndLen-2]) | int(b[i+directoryEndLen-1])<<8 + if n+directoryEndLen+i == len(b) { return i } } } return -1 } - -func read(r io.Reader, data interface{}) { - if err := binary.Read(r, binary.LittleEndian, data); err != nil { - panic(err) - } -} - -func readByteSlice(r io.Reader, l uint16) []byte { - b := make([]byte, l) - if l == 0 { - return b - } - if _, err := io.ReadFull(r, b); err != nil { - panic(err) - } - return b -} diff --git a/libgo/go/archive/zip/reader_test.go b/libgo/go/archive/zip/reader_test.go index c72cd9a2347..fd5fed2af05 100644 --- a/libgo/go/archive/zip/reader_test.go +++ b/libgo/go/archive/zip/reader_test.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "os" "testing" + "time" ) type ZipTest struct { @@ -24,8 +25,19 @@ type ZipTestFile struct { Name string 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" } +// Caution: The Mtime values found for the test files should correspond to +// the values listed with unzip -l <zipfile>. However, the values +// listed by unzip appear to be off by some hours. When creating +// fresh test files and testing them, this issue is not present. +// The test files were created in Sydney, so there might be a time +// zone issue. The time zone information does have to be encoded +// somewhere, because otherwise unzip -l could not provide a different +// time from what the archive/zip package provides, but there appears +// to be no documentation about this. + var tests = []ZipTest{ { Name: "test.zip", @@ -34,10 +46,12 @@ var tests = []ZipTest{ { Name: "test.txt", Content: []byte("This is a test text file.\n"), + Mtime: "09-05-10 12:12:02", }, { - Name: "gophercolor16x16.png", - File: "gophercolor16x16.png", + Name: "gophercolor16x16.png", + File: "gophercolor16x16.png", + Mtime: "09-05-10 15:52:58", }, }, }, @@ -45,8 +59,9 @@ var tests = []ZipTest{ Name: "r.zip", File: []ZipTestFile{ { - Name: "r/r.zip", - File: "r.zip", + Name: "r/r.zip", + File: "r.zip", + Mtime: "03-04-10 00:24:16", }, }, }, @@ -58,6 +73,7 @@ var tests = []ZipTest{ { Name: "filename", Content: []byte("This is a test textfile.\n"), + Mtime: "02-02-11 13:06:20", }, }, }, @@ -136,18 +152,36 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) { if f.Name != ft.Name { 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) + } + + size0 := f.UncompressedSize + var b bytes.Buffer r, err := f.Open() if err != nil { t.Error(err) return } + + if size1 := f.UncompressedSize; size0 != size1 { + t.Errorf("file %q changed f.UncompressedSize from %d to %d", f.Name, size0, size1) + } + _, err = io.Copy(&b, r) if err != nil { t.Error(err) return } r.Close() + var c []byte if len(ft.Content) != 0 { c = ft.Content @@ -155,10 +189,12 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) { t.Error(err) return } + if b.Len() != len(c) { t.Errorf("%s: len=%d, want %d", f.Name, b.Len(), len(c)) return } + for i, b := range b.Bytes() { if b != c[i] { t.Errorf("%s: content[%d]=%q want %q", f.Name, i, b, c[i]) diff --git a/libgo/go/archive/zip/struct.go b/libgo/go/archive/zip/struct.go index bfe0aae2e9a..1d6e70f105a 100644 --- a/libgo/go/archive/zip/struct.go +++ b/libgo/go/archive/zip/struct.go @@ -1,9 +1,32 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package zip provides support for reading and writing ZIP archives. + +See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT + +This package does not support ZIP64 or disk spanning. +*/ package zip +import "os" +import "time" + +// Compression methods. +const ( + Store uint16 = 0 + Deflate uint16 = 8 +) + const ( fileHeaderSignature = 0x04034b50 directoryHeaderSignature = 0x02014b50 directoryEndSignature = 0x06054b50 + fileHeaderLen = 30 // + filename + extra + directoryHeaderLen = 46 // + filename + extra + comment + directoryEndLen = 22 // + comment dataDescriptorLen = 12 ) @@ -13,8 +36,8 @@ type FileHeader struct { ReaderVersion uint16 Flags uint16 Method uint16 - ModifiedTime uint16 - ModifiedDate uint16 + ModifiedTime uint16 // MS-DOS time + ModifiedDate uint16 // MS-DOS date CRC32 uint32 CompressedSize uint32 UncompressedSize uint32 @@ -32,3 +55,37 @@ type directoryEnd struct { commentLen uint16 comment string } + +func recoverError(err *os.Error) { + if e := recover(); e != nil { + if osErr, ok := e.(os.Error); ok { + *err = osErr + return + } + panic(e) + } +} + +// msDosTimeToTime converts an MS-DOS date and time into a time.Time. +// 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{ + // 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), + + // 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), + } +} + +// Mtime_ns returns the modified time in ns since epoch. +// The resolution is 2s. +func (h *FileHeader) Mtime_ns() int64 { + t := msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) + return t.Seconds() * 1e9 +} diff --git a/libgo/go/archive/zip/writer.go b/libgo/go/archive/zip/writer.go new file mode 100644 index 00000000000..2065b06daac --- /dev/null +++ b/libgo/go/archive/zip/writer.go @@ -0,0 +1,244 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package zip + +import ( + "bufio" + "compress/flate" + "encoding/binary" + "hash" + "hash/crc32" + "io" + "os" +) + +// TODO(adg): support zip file comments +// TODO(adg): support specifying deflate level + +// Writer implements a zip file writer. +type Writer struct { + *countWriter + dir []*header + last *fileWriter + closed bool +} + +type header struct { + *FileHeader + offset uint32 +} + +// NewWriter returns a new Writer writing a zip file to w. +func NewWriter(w io.Writer) *Writer { + return &Writer{countWriter: &countWriter{w: bufio.NewWriter(w)}} +} + +// 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) { + if w.last != nil && !w.last.closed { + if err = w.last.close(); err != nil { + return + } + w.last = nil + } + if w.closed { + return os.NewError("zip: writer closed twice") + } + w.closed = true + + defer recoverError(&err) + + // write central directory + start := w.count + for _, h := range w.dir { + write(w, uint32(directoryHeaderSignature)) + write(w, h.CreatorVersion) + write(w, h.ReaderVersion) + write(w, h.Flags) + write(w, h.Method) + write(w, h.ModifiedTime) + write(w, h.ModifiedDate) + write(w, h.CRC32) + write(w, h.CompressedSize) + write(w, h.UncompressedSize) + write(w, uint16(len(h.Name))) + write(w, uint16(len(h.Extra))) + 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.offset) + writeBytes(w, []byte(h.Name)) + writeBytes(w, h.Extra) + writeBytes(w, []byte(h.Comment)) + } + end := w.count + + // write end record + write(w, uint32(directoryEndSignature)) + write(w, uint16(0)) // disk number + write(w, uint16(0)) // disk number where directory starts + write(w, uint16(len(w.dir))) // number of entries this disk + write(w, uint16(len(w.dir))) // number of entries total + write(w, uint32(end-start)) // size of directory + write(w, uint32(start)) // start of directory + write(w, uint16(0)) // size of comment + + return w.w.(*bufio.Writer).Flush() +} + +// Create adds a file to the zip file using the provided name. +// 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) { + header := &FileHeader{ + Name: name, + Method: Deflate, + } + return w.CreateHeader(header) +} + +// CreateHeader adds a file to the zip file using the provided FileHeader +// for the file metadata. +// 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) { + if w.last != nil && !w.last.closed { + if err := w.last.close(); err != nil { + return nil, err + } + } + + fh.Flags |= 0x8 // we will write a data descriptor + fh.CreatorVersion = 0x14 + fh.ReaderVersion = 0x14 + + fw := &fileWriter{ + zipw: w, + compCount: &countWriter{w: w}, + crc32: crc32.NewIEEE(), + } + switch fh.Method { + case Store: + fw.comp = nopCloser{fw.compCount} + case Deflate: + fw.comp = flate.NewWriter(fw.compCount, 5) + default: + return nil, UnsupportedMethod + } + fw.rawCount = &countWriter{w: fw.comp} + + h := &header{ + FileHeader: fh, + offset: uint32(w.count), + } + w.dir = append(w.dir, h) + fw.header = h + + if err := writeHeader(w, fh); err != nil { + return nil, err + } + + w.last = fw + return fw, nil +} + +func writeHeader(w io.Writer, h *FileHeader) (err os.Error) { + defer recoverError(&err) + write(w, uint32(fileHeaderSignature)) + write(w, h.ReaderVersion) + write(w, h.Flags) + write(w, h.Method) + write(w, h.ModifiedTime) + write(w, h.ModifiedDate) + write(w, h.CRC32) + write(w, h.CompressedSize) + write(w, h.UncompressedSize) + write(w, uint16(len(h.Name))) + write(w, uint16(len(h.Extra))) + writeBytes(w, []byte(h.Name)) + writeBytes(w, h.Extra) + return nil +} + +type fileWriter struct { + *header + zipw io.Writer + rawCount *countWriter + comp io.WriteCloser + compCount *countWriter + crc32 hash.Hash32 + closed bool +} + +func (w *fileWriter) Write(p []byte) (int, os.Error) { + if w.closed { + return 0, os.NewError("zip: write to closed file") + } + w.crc32.Write(p) + return w.rawCount.Write(p) +} + +func (w *fileWriter) close() (err os.Error) { + if w.closed { + return os.NewError("zip: file closed twice") + } + w.closed = true + if err = w.comp.Close(); err != nil { + return + } + + // update FileHeader + fh := w.header.FileHeader + fh.CRC32 = w.crc32.Sum32() + fh.CompressedSize = uint32(w.compCount.count) + fh.UncompressedSize = uint32(w.rawCount.count) + + // write data descriptor + defer recoverError(&err) + write(w.zipw, fh.CRC32) + write(w.zipw, fh.CompressedSize) + write(w.zipw, fh.UncompressedSize) + + return nil +} + +type countWriter struct { + w io.Writer + count int64 +} + +func (w *countWriter) Write(p []byte) (int, os.Error) { + n, err := w.w.Write(p) + w.count += int64(n) + return n, err +} + +type nopCloser struct { + io.Writer +} + +func (w nopCloser) Close() os.Error { + return nil +} + +func write(w io.Writer, data interface{}) { + if err := binary.Write(w, binary.LittleEndian, data); err != nil { + panic(err) + } +} + +func writeBytes(w io.Writer, b []byte) { + n, err := w.Write(b) + if err != nil { + panic(err) + } + if n != len(b) { + panic(io.ErrShortWrite) + } +} diff --git a/libgo/go/archive/zip/writer_test.go b/libgo/go/archive/zip/writer_test.go new file mode 100644 index 00000000000..eb2a80c3f70 --- /dev/null +++ b/libgo/go/archive/zip/writer_test.go @@ -0,0 +1,73 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package zip + +import ( + "bytes" + "io/ioutil" + "rand" + "testing" +) + +// TODO(adg): a more sophisticated test suite + +const testString = "Rabbits, guinea pigs, gophers, marsupial rats, and quolls." + +func TestWriter(t *testing.T) { + largeData := make([]byte, 1<<17) + for i := range largeData { + largeData[i] = byte(rand.Int()) + } + + // write a zip file + buf := new(bytes.Buffer) + w := NewWriter(buf) + testCreate(t, w, "foo", []byte(testString), Store) + testCreate(t, w, "bar", largeData, Deflate) + if err := w.Close(); err != nil { + t.Fatal(err) + } + + // read it back + r, err := NewReader(sliceReaderAt(buf.Bytes()), int64(buf.Len())) + if err != nil { + t.Fatal(err) + } + testReadFile(t, r.File[0], []byte(testString)) + testReadFile(t, r.File[1], largeData) +} + +func testCreate(t *testing.T, w *Writer, name string, data []byte, method uint16) { + header := &FileHeader{ + Name: name, + Method: method, + } + f, err := w.CreateHeader(header) + if err != nil { + t.Fatal(err) + } + _, err = f.Write(data) + if err != nil { + t.Fatal(err) + } +} + +func testReadFile(t *testing.T, f *File, data []byte) { + rc, err := f.Open() + if err != nil { + t.Fatal("opening:", err) + } + b, err := ioutil.ReadAll(rc) + if err != nil { + t.Fatal("reading:", err) + } + err = rc.Close() + if err != nil { + t.Fatal("closing:", err) + } + if !bytes.Equal(b, data) { + t.Errorf("File contents %q, want %q", b, data) + } +} diff --git a/libgo/go/archive/zip/zip_test.go b/libgo/go/archive/zip/zip_test.go new file mode 100644 index 00000000000..0f71fdfac17 --- /dev/null +++ b/libgo/go/archive/zip/zip_test.go @@ -0,0 +1,57 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Tests that involve both reading and writing. + +package zip + +import ( + "bytes" + "fmt" + "os" + "testing" +) + +type stringReaderAt string + +func (s stringReaderAt) ReadAt(p []byte, off int64) (n int, err os.Error) { + if off >= int64(len(s)) { + return 0, os.EOF + } + n = copy(p, s[off:]) + return +} + +func TestOver65kFiles(t *testing.T) { + if testing.Short() { + t.Logf("slow test; skipping") + return + } + buf := new(bytes.Buffer) + w := NewWriter(buf) + const nFiles = (1 << 16) + 42 + for i := 0; i < nFiles; i++ { + _, err := w.Create(fmt.Sprintf("%d.dat", i)) + if err != nil { + t.Fatalf("creating file %d: %v", i, err) + } + } + if err := w.Close(); err != nil { + t.Fatalf("Writer.Close: %v", err) + } + rat := stringReaderAt(buf.String()) + zr, err := NewReader(rat, int64(len(rat))) + if err != nil { + t.Fatalf("NewReader: %v", err) + } + if got := len(zr.File); got != nFiles { + t.Fatalf("File contains %d files, want %d", got, nFiles) + } + for i := 0; i < nFiles; i++ { + want := fmt.Sprintf("%d.dat", i) + if zr.File[i].Name != want { + t.Fatalf("File(%d) = %q, want %q", i, zr.File[i].Name, want) + } + } +} |